STM32 #5

홍태준·2026년 1월 17일

STM32

목록 보기
5/15
post-thumbnail

Week 1 Day 5: 미니 프로젝트 - 버튼으로 LED 패턴 변경

학습 목표

  • 지금까지 배운 GPIO, 인터럽트, 디바운싱 기법을 통합 활용한다
  • 상태 머신(State Machine) 패턴을 실전에 적용한다
  • 여러 버튼과 LED를 조합한 인터랙티브 시스템을 구현한다
  • 코드 구조화 및 모듈화 방법을 익힌다
  • 디버깅 및 문제 해결 능력을 향상시킨다

1. 프로젝트 개요

이번 프로젝트는 1주차에 배운 모든 내용을 종합하는 실습입니다. 버튼 입력으로 다양한 LED 패턴을 제어하며, 실제 제품과 유사한 인터페이스를 구현합니다.

1.1 프로젝트 목표

하드웨어 구성

4개의 LED (PD12, PD13, PD14, PD15)
3개의 버튼:
  - 버튼 1 (PA0): 패턴 변경
  - 버튼 2 (PA1): 속도 조절
  - 버튼 3 (PA2): 시작/정지

기능 요구사항
1. 최소 5가지 LED 패턴 구현
2. 버튼으로 패턴 순환 선택
3. 패턴 속도 3단계 조절 (느림/보통/빠름)
4. 시작/정지 기능
5. 인터럽트 방식 버튼 입력 (디바운싱 포함)
6. 전원 켤 때 마지막 설정 유지 (선택사항)

1.2 학습 포인트

이 프로젝트를 통해 단순히 동작하는 코드를 넘어, 유지보수 가능하고 확장 가능한 코드 작성법을 배웁니다.

  • 상태 관리: 현재 패턴, 속도, 실행 상태를 체계적으로 관리
  • 모듈화: 패턴 함수를 독립적으로 분리
  • 디버깅: 버튼 입력과 LED 출력을 UART로 모니터링
  • 코드 품질: 가독성, 확장성, 재사용성 고려

2. 프로젝트 설계

프로젝트를 시작하기 전에 전체 구조를 설계하는 것이 중요합니다. 상태 다이어그램과 데이터 구조를 먼저 정의하면 구현이 훨씬 수월해집니다.

2.1 상태 다이어그램

┌─────────────┐
│   STOPPED  │ ◄──┐
└─────────────┘    │
       │          │ Button 3
       │ Button 3 │ (Stop)
       ▼          │
┌─────────────┐    │
│   RUNNING  │ ───┘
└─────────────┘
       │
       │ Button 1 (패턴 변경)
       │ Button 2 (속도 변경)
       │
    [Pattern Update]

상태 정의

  • STOPPED: LED 모두 꺼짐, 버튼 입력만 처리
  • RUNNING: 선택된 패턴 실행 중

2.2 데이터 구조 설계

전역 변수를 최소화하고 구조체로 관련 데이터를 묶으면 코드가 깔끔해집니다.

/* USER CODE BEGIN 0 */
// 시스템 상태
typedef enum {
  SYSTEM_STOPPED,
  SYSTEM_RUNNING
} SystemState_t;

// LED 패턴 종류
typedef enum {
  PATTERN_ALL_ON,
  PATTERN_ALL_BLINK,
  PATTERN_SEQUENTIAL,
  PATTERN_PING_PONG,
  PATTERN_BINARY_COUNT,
  PATTERN_RANDOM,
  PATTERN_MAX  // 패턴 개수
} LedPattern_t;

// 속도 단계
typedef enum {
  SPEED_SLOW = 0,    // 500ms
  SPEED_NORMAL = 1,  // 250ms
  SPEED_FAST = 2,    // 100ms
  SPEED_MAX
} Speed_t;

// 시스템 컨텍스트
typedef struct {
  SystemState_t state;
  LedPattern_t current_pattern;
  Speed_t speed;
  uint32_t last_update_tick;
  uint8_t pattern_step;  // 패턴 내부 단계
} SystemContext_t;

SystemContext_t sys_ctx = {
  .state = SYSTEM_STOPPED,
  .current_pattern = PATTERN_ALL_BLINK,
  .speed = SPEED_NORMAL,
  .last_update_tick = 0,
  .pattern_step = 0
};
/* USER CODE END 0 */

2.3 함수 인터페이스 설계

각 기능을 독립적인 함수로 분리하면 테스트와 수정이 쉬워집니다.

/* USER CODE BEGIN 0 */
// LED 제어
void led_set_all(uint8_t state);
void led_set_pattern(uint8_t pattern);  // 4비트 패턴

// 패턴 함수 (각 패턴마다)
void pattern_all_on(void);
void pattern_all_blink(void);
void pattern_sequential(void);
void pattern_ping_pong(void);
void pattern_binary_count(void);
void pattern_random(void);

// 시스템 제어
void system_start(void);
void system_stop(void);
void system_update(void);
void system_change_pattern(void);
void system_change_speed(void);

// 버튼 핸들러
void button_pattern_pressed(void);
void button_speed_pressed(void);
void button_startstop_pressed(void);

// 유틸리티
uint32_t get_speed_delay_ms(void);
void print_status(void);
/* USER CODE END 0 */

3. 하드웨어 설정

STM32CubeMX에서 필요한 GPIO와 인터럽트를 설정합니다.

3.1 GPIO 설정

LED 출력 (GPIOD)

PD12: GPIO_Output (LED1)
PD13: GPIO_Output (LED2)
PD14: GPIO_Output (LED3)
PD15: GPIO_Output (LED4)

설정:
  - GPIO output level: Low
  - GPIO mode: Output Push Pull
  - GPIO Pull-up/Pull-down: No pull-up and no pull-down
  - Maximum output speed: Low
  - User Label: LED1, LED2, LED3, LED4

버튼 입력 (GPIOA)

PA0: GPIO_EXTI0 (Button 1 - Pattern)
PA1: GPIO_EXTI1 (Button 2 - Speed)
PA2: GPIO_EXTI2 (Button 3 - Start/Stop)

설정:
  - GPIO mode: External Interrupt Mode with Falling edge trigger detection
  - GPIO Pull-up/Pull-down: Pull-up
  - User Label: BTN_PATTERN, BTN_SPEED, BTN_STARTSTOP

3.2 NVIC 설정

NVIC Configuration

EXTI line0 interrupt: Enabled, Priority 5
EXTI line1 interrupt: Enabled, Priority 5
EXTI line2 interrupt: Enabled, Priority 5

3.3 UART 설정 (디버깅용)

USART1 Mode: Asynchronous
Baud Rate: 115200
Word Length: 8 Bits
Parity: None
Stop Bits: 1

4. 핵심 기능 구현

각 기능을 단계별로 구현하며, 테스트 가능한 작은 단위로 나누어 진행합니다.

4.1 LED 기본 제어 함수

/* USER CODE BEGIN 0 */
// LED 핀 정의
#define LED1_PIN GPIO_PIN_12
#define LED2_PIN GPIO_PIN_13
#define LED3_PIN GPIO_PIN_14
#define LED4_PIN GPIO_PIN_15
#define LED_PORT GPIOD
#define ALL_LEDS (LED1_PIN | LED2_PIN | LED3_PIN | LED4_PIN)

// 모든 LED 제어
void led_set_all(uint8_t state)
{
  HAL_GPIO_WritePin(LED_PORT, ALL_LEDS, 
                    state ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

// 4비트 패턴으로 LED 제어 (0000 ~ 1111)
void led_set_pattern(uint8_t pattern)
{
  HAL_GPIO_WritePin(LED_PORT, LED1_PIN, 
                    (pattern & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);
  HAL_GPIO_WritePin(LED_PORT, LED2_PIN, 
                    (pattern & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);
  HAL_GPIO_WritePin(LED_PORT, LED3_PIN, 
                    (pattern & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET);
  HAL_GPIO_WritePin(LED_PORT, LED4_PIN, 
                    (pattern & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

// 개별 LED 토글
void led_toggle(uint16_t led_pin)
{
  HAL_GPIO_TogglePin(LED_PORT, led_pin);
}
/* USER CODE END 0 */

4.2 속도 관리

속도 설정에 따라 패턴 업데이트 주기를 결정합니다.

/* USER CODE BEGIN 0 */
uint32_t get_speed_delay_ms(void)
{
  switch(sys_ctx.speed)
  {
    case SPEED_SLOW:
      return 500;
    case SPEED_NORMAL:
      return 250;
    case SPEED_FAST:
      return 100;
    default:
      return 250;
  }
}

const char* get_speed_name(void)
{
  switch(sys_ctx.speed)
  {
    case SPEED_SLOW:   return "Slow";
    case SPEED_NORMAL: return "Normal";
    case SPEED_FAST:   return "Fast";
    default:           return "Unknown";
  }
}
/* USER CODE END 0 */

4.3 패턴 함수 구현

각 패턴을 독립적인 함수로 구현하면 새로운 패턴 추가가 쉽습니다.

패턴 1: 전체 켜기

/* USER CODE BEGIN 0 */
void pattern_all_on(void)
{
  led_set_all(1);
  sys_ctx.pattern_step = 0;  // 정적 패턴
}
/* USER CODE END 0 */

패턴 2: 전체 깜박임

/* USER CODE BEGIN 0 */
void pattern_all_blink(void)
{
  if(sys_ctx.pattern_step == 0)
  {
    led_set_all(1);
    sys_ctx.pattern_step = 1;
  }
  else
  {
    led_set_all(0);
    sys_ctx.pattern_step = 0;
  }
}
/* USER CODE END 0 */

패턴 3: 순차 점등

/* USER CODE BEGIN 0 */
void pattern_sequential(void)
{
  // 0001 → 0010 → 0100 → 1000 → 0001
  uint8_t pattern = 1 << (sys_ctx.pattern_step % 4);
  led_set_pattern(pattern);
  sys_ctx.pattern_step++;
}
/* USER CODE END 0 */

패턴 4: 핑퐁 (왕복)

/* USER CODE BEGIN 0 */
void pattern_ping_pong(void)
{
  // 0001 → 0010 → 0100 → 1000 → 0100 → 0010 → 0001
  static int8_t direction = 1;
  static uint8_t position = 0;
  
  uint8_t pattern = 1 << position;
  led_set_pattern(pattern);
  
  position += direction;
  
  if(position == 3)
    direction = -1;
  else if(position == 0)
    direction = 1;
  
  sys_ctx.pattern_step = position;
}
/* USER CODE END 0 */

패턴 5: 이진 카운터

/* USER CODE BEGIN 0 */
void pattern_binary_count(void)
{
  // 0000 → 0001 → 0010 → ... → 1111 → 0000
  uint8_t pattern = sys_ctx.pattern_step % 16;
  led_set_pattern(pattern);
  sys_ctx.pattern_step++;
}
/* USER CODE END 0 */

패턴 6: 랜덤

/* USER CODE BEGIN 0 */
#include <stdlib.h>

void pattern_random(void)
{
  uint8_t pattern = rand() % 16;
  led_set_pattern(pattern);
  sys_ctx.pattern_step++;
}
/* USER CODE END 0 */

4.4 패턴 실행 함수

현재 선택된 패턴에 따라 적절한 패턴 함수를 호출합니다.

/* USER CODE BEGIN 0 */
void execute_current_pattern(void)
{
  switch(sys_ctx.current_pattern)
  {
    case PATTERN_ALL_ON:
      pattern_all_on();
      break;
    case PATTERN_ALL_BLINK:
      pattern_all_blink();
      break;
    case PATTERN_SEQUENTIAL:
      pattern_sequential();
      break;
    case PATTERN_PING_PONG:
      pattern_ping_pong();
      break;
    case PATTERN_BINARY_COUNT:
      pattern_binary_count();
      break;
    case PATTERN_RANDOM:
      pattern_random();
      break;
    default:
      pattern_all_on();
      break;
  }
}

const char* get_pattern_name(void)
{
  switch(sys_ctx.current_pattern)
  {
    case PATTERN_ALL_ON:        return "All ON";
    case PATTERN_ALL_BLINK:     return "All Blink";
    case PATTERN_SEQUENTIAL:    return "Sequential";
    case PATTERN_PING_PONG:     return "Ping Pong";
    case PATTERN_BINARY_COUNT:  return "Binary Count";
    case PATTERN_RANDOM:        return "Random";
    default:                    return "Unknown";
  }
}
/* USER CODE END 0 */

5. 시스템 제어 함수

시스템 상태를 관리하는 함수들입니다. 시작, 정지, 업데이트, 설정 변경 등을 처리합니다.

5.1 시작/정지

/* USER CODE BEGIN 0 */
void system_start(void)
{
  if(sys_ctx.state == SYSTEM_STOPPED)
  {
    sys_ctx.state = SYSTEM_RUNNING;
    sys_ctx.pattern_step = 0;
    sys_ctx.last_update_tick = HAL_GetTick();
    printf("System STARTED - Pattern: %s, Speed: %s\r\n", 
           get_pattern_name(), get_speed_name());
  }
}

void system_stop(void)
{
  if(sys_ctx.state == SYSTEM_RUNNING)
  {
    sys_ctx.state = SYSTEM_STOPPED;
    led_set_all(0);  // 모든 LED 끄기
    printf("System STOPPED\r\n");
  }
}
/* USER CODE END 0 */

5.2 패턴 및 속도 변경

/* USER CODE BEGIN 0 */
void system_change_pattern(void)
{
  sys_ctx.current_pattern = (sys_ctx.current_pattern + 1) % PATTERN_MAX;
  sys_ctx.pattern_step = 0;  // 패턴 단계 초기화
  
  printf("Pattern changed: %s\r\n", get_pattern_name());
  
  // 실행 중이면 즉시 새 패턴 표시
  if(sys_ctx.state == SYSTEM_RUNNING)
  {
    execute_current_pattern();
  }
}

void system_change_speed(void)
{
  sys_ctx.speed = (sys_ctx.speed + 1) % SPEED_MAX;
  printf("Speed changed: %s (%lu ms)\r\n", 
         get_speed_name(), get_speed_delay_ms());
}
/* USER CODE END 0 */

5.3 시스템 업데이트 (메인 루프)

메인 루프에서 호출하여 패턴을 주기적으로 업데이트합니다.

/* USER CODE BEGIN 0 */
void system_update(void)
{
  if(sys_ctx.state != SYSTEM_RUNNING)
    return;
  
  uint32_t current_tick = HAL_GetTick();
  uint32_t delay = get_speed_delay_ms();
  
  if((current_tick - sys_ctx.last_update_tick) >= delay)
  {
    sys_ctx.last_update_tick = current_tick;
    execute_current_pattern();
  }
}
/* USER CODE END 0 */

6. 버튼 인터럽트 처리

인터럽트 콜백에서는 최소한의 처리만 하고, 디바운싱을 적용합니다.

6.1 인터럽트 콜백

/* USER CODE BEGIN 0 */
// 디바운싱용 타임스탬프
uint32_t last_btn_pattern_time = 0;
uint32_t last_btn_speed_time = 0;
uint32_t last_btn_startstop_time = 0;

#define DEBOUNCE_DELAY 200  // 200ms

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  uint32_t current_time = HAL_GetTick();
  
  if(GPIO_Pin == GPIO_PIN_0)  // Button 1 - Pattern
  {
    if((current_time - last_btn_pattern_time) > DEBOUNCE_DELAY)
    {
      last_btn_pattern_time = current_time;
      button_pattern_pressed();
    }
  }
  else if(GPIO_Pin == GPIO_PIN_1)  // Button 2 - Speed
  {
    if((current_time - last_btn_speed_time) > DEBOUNCE_DELAY)
    {
      last_btn_speed_time = current_time;
      button_speed_pressed();
    }
  }
  else if(GPIO_Pin == GPIO_PIN_2)  // Button 3 - Start/Stop
  {
    if((current_time - last_btn_startstop_time) > DEBOUNCE_DELAY)
    {
      last_btn_startstop_time = current_time;
      button_startstop_pressed();
    }
  }
}
/* USER CODE END 0 */

6.2 버튼 핸들러 구현

/* USER CODE BEGIN 0 */
void button_pattern_pressed(void)
{
  system_change_pattern();
}

void button_speed_pressed(void)
{
  system_change_speed();
}

void button_startstop_pressed(void)
{
  if(sys_ctx.state == SYSTEM_STOPPED)
  {
    system_start();
  }
  else
  {
    system_stop();
  }
}
/* USER CODE END 0 */

7. 상태 모니터링

UART를 통해 시스템 상태를 모니터링하면 디버깅이 훨씬 쉬워집니다.

7.1 상태 출력

/* USER CODE BEGIN 0 */
void print_status(void)
{
  printf("\r\n=== LED Pattern Controller Status ===\r\n");
  printf("State:   %s\r\n", 
         sys_ctx.state == SYSTEM_RUNNING ? "RUNNING" : "STOPPED");
  printf("Pattern: %s\r\n", get_pattern_name());
  printf("Speed:   %s (%lu ms)\r\n", 
         get_speed_name(), get_speed_delay_ms());
  printf("=====================================\r\n\n");
}

void print_help(void)
{
  printf("\r\n=== LED Pattern Controller ===\r\n");
  printf("Button 1: Change Pattern\r\n");
  printf("Button 2: Change Speed\r\n");
  printf("Button 3: Start/Stop\r\n");
  printf("\r\nPatterns:\r\n");
  printf("  1. All ON\r\n");
  printf("  2. All Blink\r\n");
  printf("  3. Sequential\r\n");
  printf("  4. Ping Pong\r\n");
  printf("  5. Binary Count\r\n");
  printf("  6. Random\r\n");
  printf("\r\nSpeed Levels:\r\n");
  printf("  Slow:   500ms\r\n");
  printf("  Normal: 250ms\r\n");
  printf("  Fast:   100ms\r\n");
  printf("==============================\r\n\n");
}
/* USER CODE END 0 */

8. 메인 함수 통합

모든 기능을 메인 함수에서 통합합니다.

8.1 초기화

/* USER CODE BEGIN 2 */
// UART printf 리다이렉션 (필요 시)
// syscalls.c에 _write 함수 구현 필요

printf("\r\n\r\n");
printf("====================================\r\n");
printf("  LED Pattern Controller v1.0\r\n");
printf("====================================\r\n");

print_help();
print_status();

// 랜덤 시드 초기화
srand(HAL_GetTick());

/* USER CODE END 2 */

8.2 메인 루프

/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
  // 시스템 업데이트 (패턴 실행)
  system_update();
  
  // 짧은 딜레이 (CPU 부하 감소)
  HAL_Delay(10);
}
/* USER CODE END 3 */

9. 고급 기능 추가

기본 기능이 완성되면 추가 기능을 구현하여 프로젝트를 발전시킬 수 있습니다.

9.1 버튼 길게 누르기

버튼을 길게 누르면 다른 기능을 실행하도록 확장할 수 있습니다.

/* USER CODE BEGIN 0 */
#define LONG_PRESS_TIME 1000  // 1초

typedef struct {
  uint32_t press_time;
  uint8_t is_pressed;
} ButtonState_t;

ButtonState_t btn_pattern_state = {0, 0};

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  uint32_t current_time = HAL_GetTick();
  
  if(GPIO_Pin == GPIO_PIN_0)  // Button 1
  {
    GPIO_PinState pin_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
    
    if(pin_state == GPIO_PIN_RESET)  // 버튼 눌림
    {
      btn_pattern_state.press_time = current_time;
      btn_pattern_state.is_pressed = 1;
    }
    else  // 버튼 떼어짐
    {
      if(btn_pattern_state.is_pressed)
      {
        uint32_t press_duration = current_time - btn_pattern_state.press_time;
        
        if(press_duration >= LONG_PRESS_TIME)
        {
          // 길게 누름: 모든 패턴 순회 데모
          demo_all_patterns();
        }
        else
        {
          // 짧게 누름: 패턴 변경
          button_pattern_pressed();
        }
        
        btn_pattern_state.is_pressed = 0;
      }
    }
  }
  // 다른 버튼 처리...
}

void demo_all_patterns(void)
{
  printf("Demo: All Patterns\r\n");
  
  SystemState_t original_state = sys_ctx.state;
  sys_ctx.state = SYSTEM_RUNNING;
  
  for(uint8_t i = 0; i < PATTERN_MAX; i++)
  {
    sys_ctx.current_pattern = i;
    sys_ctx.pattern_step = 0;
    
    printf("  Pattern %d: %s\r\n", i+1, get_pattern_name());
    
    for(int j = 0; j < 10; j++)
    {
      execute_current_pattern();
      HAL_Delay(200);
    }
  }
  
  sys_ctx.state = original_state;
  printf("Demo completed\r\n");
}
/* USER CODE END 0 */

9.2 설정 저장 (EEPROM 시뮬레이션)

전원을 껐다 켜도 마지막 설정을 유지하도록 구현할 수 있습니다.

/* USER CODE BEGIN 0 */
// Flash 메모리 마지막 섹터에 저장 (간단한 예제)
// 실제로는 EEPROM 에뮬레이션 라이브러리 사용 권장

#define SETTINGS_MAGIC 0x12345678
#define SETTINGS_ADDRESS 0x080E0000  // Sector 11 시작 주소

typedef struct {
  uint32_t magic;
  LedPattern_t pattern;
  Speed_t speed;
  uint32_t checksum;
} SavedSettings_t;

uint32_t calculate_checksum(SavedSettings_t* settings)
{
  return settings->magic ^ settings->pattern ^ settings->speed;
}

void save_settings(void)
{
  SavedSettings_t settings;
  settings.magic = SETTINGS_MAGIC;
  settings.pattern = sys_ctx.current_pattern;
  settings.speed = sys_ctx.speed;
  settings.checksum = calculate_checksum(&settings);
  
  // Flash 쓰기 코드 (생략)
  // HAL_FLASH_Unlock();
  // HAL_FLASH_Program(...);
  // HAL_FLASH_Lock();
  
  printf("Settings saved\r\n");
}

void load_settings(void)
{
  SavedSettings_t* settings = (SavedSettings_t*)SETTINGS_ADDRESS;
  
  if(settings->magic == SETTINGS_MAGIC &&
     settings->checksum == calculate_checksum(settings))
  {
    sys_ctx.current_pattern = settings->pattern;
    sys_ctx.speed = settings->speed;
    printf("Settings loaded\r\n");
  }
  else
  {
    printf("No saved settings found, using defaults\r\n");
  }
}
/* USER CODE END 0 */

9.3 밝기 조절 (다음 주 PWM 예고)

다음 주에 배울 PWM을 사용하면 LED 밝기도 조절할 수 있습니다.

/* USER CODE BEGIN 0 */
// Week 2에서 배울 내용 미리보기
uint8_t brightness = 50;  // 0 ~ 100%

void adjust_brightness(int8_t delta)
{
  int16_t new_brightness = brightness + delta;
  
  if(new_brightness < 0)
    new_brightness = 0;
  else if(new_brightness > 100)
    new_brightness = 100;
  
  brightness = (uint8_t)new_brightness;
  
  // PWM Duty Cycle 조절 (Week 2에서 구현)
  // __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, brightness * 10);
  
  printf("Brightness: %d%%\r\n", brightness);
}
/* USER CODE END 0 */

10. 테스트 및 디버깅

체계적인 테스트로 버그를 사전에 발견하고 수정합니다.

10.1 단위 테스트

LED 제어 테스트

/* USER CODE BEGIN 2 */
// LED 테스트 시퀀스
printf("LED Test Sequence\r\n");

// 개별 LED 테스트
for(int i = 0; i < 4; i++)
{
  uint16_t led = GPIO_PIN_12 << i;
  printf("Testing LED%d...\r\n", i+1);
  HAL_GPIO_WritePin(LED_PORT, led, GPIO_PIN_SET);
  HAL_Delay(500);
  HAL_GPIO_WritePin(LED_PORT, led, GPIO_PIN_RESET);
  HAL_Delay(200);
}

// 전체 LED 테스트
printf("All LEDs ON\r\n");
led_set_all(1);
HAL_Delay(1000);
led_set_all(0);
HAL_Delay(500);

printf("LED Test Complete\r\n\n");
/* USER CODE END 2 */

버튼 입력 테스트

/* USER CODE BEGIN 3 */
// 버튼 상태 모니터링
static uint32_t last_monitor_time = 0;

if((HAL_GetTick() - last_monitor_time) > 1000)
{
  last_monitor_time = HAL_GetTick();
  
  printf("Button Status: ");
  printf("BTN1=%d ", HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
  printf("BTN2=%d ", HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
  printf("BTN3=%d\r\n", HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_RESET);
}
/* USER CODE END 3 */

10.2 통합 테스트

패턴 전환 테스트

/* USER CODE BEGIN 2 */
void test_all_patterns(void)
{
  printf("\n=== Pattern Test ===\n");
  
  sys_ctx.state = SYSTEM_RUNNING;
  
  for(uint8_t i = 0; i < PATTERN_MAX; i++)
  {
    sys_ctx.current_pattern = i;
    sys_ctx.pattern_step = 0;
    
    printf("Testing: %s\n", get_pattern_name());
    
    for(int j = 0; j < 5; j++)
    {
      execute_current_pattern();
      HAL_Delay(300);
    }
  }
  
  sys_ctx.state = SYSTEM_STOPPED;
  led_set_all(0);
  
  printf("Pattern test completed\n\n");
}

// main() 초기화 후 호출
// test_all_patterns();
/* USER CODE END 2 */

10.3 디버깅 팁

문제가 발생했을 때 빠르게 원인을 찾는 방법입니다.

문제: 버튼이 반응하지 않음

// 1. 인터럽트가 발생하는지 확인
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  printf("IRQ: Pin %d\r\n", GPIO_Pin);  // 추가
  // 기존 코드...
}

// 2. Pull-up 확인
// CubeMX에서 GPIO Pull-up 설정 확인

// 3. NVIC 활성화 확인
printf("NVIC State: %d\r\n", 
       HAL_NVIC_GetActive(EXTI0_IRQn));

문제: LED가 의도대로 동작하지 않음

// 1. 패턴 실행 상태 출력
void execute_current_pattern(void)
{
  printf("[%lu] Pattern: %s, Step: %d\r\n", 
         HAL_GetTick(), 
         get_pattern_name(), 
         sys_ctx.pattern_step);
  
  // 기존 패턴 함수 호출...
}

// 2. LED 상태 읽기
uint8_t led_state = 0;
led_state |= (HAL_GPIO_ReadPin(LED_PORT, LED1_PIN) << 0);
led_state |= (HAL_GPIO_ReadPin(LED_PORT, LED2_PIN) << 1);
led_state |= (HAL_GPIO_ReadPin(LED_PORT, LED3_PIN) << 2);
led_state |= (HAL_GPIO_ReadPin(LED_PORT, LED4_PIN) << 3);
printf("LED State: 0x%X\r\n", led_state);

11. 실습 과제

과제 1: 새 패턴 추가

기존 6개 패턴에 자신만의 패턴을 추가해보세요.

요구사항

  • 최소 2개의 새로운 패턴 추가
  • 패턴 이름 및 설명 작성
  • 패턴 함수 구현

추천 패턴
1. Chase: 두 개의 LED가 서로 쫓아감
2. Breathe: LED가 서서히 밝아졌다 어두워짐 (소프트웨어 PWM)
3. Traffic Light: 신호등처럼 빨강→노랑→초록 순환
4. SOS: 모스 부호로 SOS 신호

힌트: Chase 패턴

void pattern_chase(void)
{
  // LED1과 LED3이 서로 반대로 움직임
  uint8_t pos1 = sys_ctx.pattern_step % 4;
  uint8_t pos2 = (sys_ctx.pattern_step + 2) % 4;
  
  uint8_t pattern = (1 << pos1) | (1 << pos2);
  led_set_pattern(pattern);
  
  sys_ctx.pattern_step++;
}

과제 2: 4번째 버튼 추가

4번째 버튼(PA3)을 추가하여 밝기 조절 기능을 구현하세요.

요구사항

  • PA3 버튼 추가 (EXTI3)
  • 버튼 누를 때마다 밝기 단계 변경 (어두움/중간/밝음)
  • 소프트웨어 PWM으로 밝기 조절
  • 현재 밝기 단계를 UART로 출력

힌트: 소프트웨어 PWM

uint8_t brightness_level = 1;  // 0: 어두움, 1: 중간, 2: 밝음

void led_set_pattern_pwm(uint8_t pattern)
{
  uint8_t duty = 0;
  
  switch(brightness_level)
  {
    case 0: duty = 10; break;   // 10% 듀티
    case 1: duty = 50; break;   // 50% 듀티
    case 2: duty = 100; break;  // 100% 듀티
  }
  
  // 간단한 소프트웨어 PWM (1ms 주기)
  for(int i = 0; i < 100; i++)
  {
    if(i < duty)
      HAL_GPIO_WritePin(LED_PORT, pattern, GPIO_PIN_SET);
    else
      HAL_GPIO_WritePin(LED_PORT, pattern, GPIO_PIN_RESET);
    
    HAL_Delay(0);  // 짧은 딜레이
  }
}

과제 3: UART 명령어 인터페이스

UART로 명령어를 입력받아 시스템을 제어하세요.

요구사항

  • 명령어 파싱 구현
  • 지원 명령어:
    • start: 시스템 시작
    • stop: 시스템 정지
    • pattern <n>: 패턴 n번으로 변경
    • speed <s>: 속도 변경 (slow/normal/fast)
    • status: 현재 상태 출력
    • help: 도움말 출력

힌트: 명령어 파싱

#define CMD_BUFFER_SIZE 64

uint8_t rx_byte;
char cmd_buffer[CMD_BUFFER_SIZE];
uint16_t cmd_index = 0;
volatile uint8_t cmd_ready = 0;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart->Instance == USART1)
  {
    if(rx_byte == '\r' || rx_byte == '\n')
    {
      if(cmd_index > 0)
      {
        cmd_buffer[cmd_index] = '\0';
        cmd_ready = 1;
      }
    }
    else if(cmd_index < CMD_BUFFER_SIZE - 1)
    {
      cmd_buffer[cmd_index++] = rx_byte;
    }
    
    HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
  }
}

void process_command(void)
{
  if(strcmp(cmd_buffer, "start") == 0)
  {
    system_start();
  }
  else if(strcmp(cmd_buffer, "stop") == 0)
  {
    system_stop();
  }
  else if(strncmp(cmd_buffer, "pattern ", 8) == 0)
  {
    uint8_t pattern_num = atoi(&cmd_buffer[8]);
    if(pattern_num > 0 && pattern_num <= PATTERN_MAX)
    {
      sys_ctx.current_pattern = pattern_num - 1;
      printf("Pattern set to: %s\r\n", get_pattern_name());
    }
  }
  else if(strcmp(cmd_buffer, "status") == 0)
  {
    print_status();
  }
  else if(strcmp(cmd_buffer, "help") == 0)
  {
    print_help();
  }
  else
  {
    printf("Unknown command: %s\r\n", cmd_buffer);
  }
  
  cmd_index = 0;
  cmd_ready = 0;
}

12. 학습 정리

오늘 배운 내용

  • 상태 머신을 사용한 시스템 설계
  • 여러 GPIO 입출력을 조합한 인터랙티브 시스템 구현
  • 코드 모듈화 및 구조화 방법
  • 디바운싱을 적용한 안정적인 버튼 입력 처리
  • UART를 통한 디버깅 및 모니터링
  • 패턴 함수 설계 및 확장 가능한 구조

핵심 개념

1. 상태 머신 패턴

- 시스템의 상태를 명확히 정의
- 상태 전환 규칙 수립
- 각 상태에서의 동작 정의
- 확장 및 유지보수 용이

2. 코드 구조화

- 데이터 구조체로 관련 정보 묶기
- 기능별 함수 분리
- 인터페이스 명확히 정의
- 재사용 가능한 모듈 작성

3. 디버깅 전략

- UART로 상태 모니터링
- 단위 테스트 작성
- 문제 발생 시 체계적 접근
- 로그 출력으로 흐름 추적

1주차 전체 복습

Day주제핵심 내용
1개발환경STM32CubeMX, VSCode, OpenOCD
2GPIO 출력LED 제어, HAL 라이브러리
3GPIO 입력버튼 인터럽트, EXTI
4디바운싱타임스탬프, 상태 머신 방식
5통합 프로젝트상태 머신, 모듈화, 확장성

profile
당신의 코딩 메이트

0개의 댓글