이번 프로젝트는 1주차에 배운 모든 내용을 종합하는 실습입니다. 버튼 입력으로 다양한 LED 패턴을 제어하며, 실제 제품과 유사한 인터페이스를 구현합니다.
하드웨어 구성
4개의 LED (PD12, PD13, PD14, PD15)
3개의 버튼:
- 버튼 1 (PA0): 패턴 변경
- 버튼 2 (PA1): 속도 조절
- 버튼 3 (PA2): 시작/정지
기능 요구사항
1. 최소 5가지 LED 패턴 구현
2. 버튼으로 패턴 순환 선택
3. 패턴 속도 3단계 조절 (느림/보통/빠름)
4. 시작/정지 기능
5. 인터럽트 방식 버튼 입력 (디바운싱 포함)
6. 전원 켤 때 마지막 설정 유지 (선택사항)
이 프로젝트를 통해 단순히 동작하는 코드를 넘어, 유지보수 가능하고 확장 가능한 코드 작성법을 배웁니다.
프로젝트를 시작하기 전에 전체 구조를 설계하는 것이 중요합니다. 상태 다이어그램과 데이터 구조를 먼저 정의하면 구현이 훨씬 수월해집니다.
┌─────────────┐
│ STOPPED │ ◄──┐
└─────────────┘ │
│ │ Button 3
│ Button 3 │ (Stop)
▼ │
┌─────────────┐ │
│ RUNNING │ ───┘
└─────────────┘
│
│ Button 1 (패턴 변경)
│ Button 2 (속도 변경)
│
[Pattern Update]
상태 정의
전역 변수를 최소화하고 구조체로 관련 데이터를 묶으면 코드가 깔끔해집니다.
/* 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 */
각 기능을 독립적인 함수로 분리하면 테스트와 수정이 쉬워집니다.
/* 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 */
STM32CubeMX에서 필요한 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
NVIC Configuration
EXTI line0 interrupt: Enabled, Priority 5
EXTI line1 interrupt: Enabled, Priority 5
EXTI line2 interrupt: Enabled, Priority 5
USART1 Mode: Asynchronous
Baud Rate: 115200
Word Length: 8 Bits
Parity: None
Stop Bits: 1
각 기능을 단계별로 구현하며, 테스트 가능한 작은 단위로 나누어 진행합니다.
/* 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 */
속도 설정에 따라 패턴 업데이트 주기를 결정합니다.
/* 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 */
각 패턴을 독립적인 함수로 구현하면 새로운 패턴 추가가 쉽습니다.
패턴 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 */
현재 선택된 패턴에 따라 적절한 패턴 함수를 호출합니다.
/* 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 */
시스템 상태를 관리하는 함수들입니다. 시작, 정지, 업데이트, 설정 변경 등을 처리합니다.
/* 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 */
/* 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 */
메인 루프에서 호출하여 패턴을 주기적으로 업데이트합니다.
/* 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 */
인터럽트 콜백에서는 최소한의 처리만 하고, 디바운싱을 적용합니다.
/* 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 */
/* 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 */
UART를 통해 시스템 상태를 모니터링하면 디버깅이 훨씬 쉬워집니다.
/* 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 */
모든 기능을 메인 함수에서 통합합니다.
/* 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 */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// 시스템 업데이트 (패턴 실행)
system_update();
// 짧은 딜레이 (CPU 부하 감소)
HAL_Delay(10);
}
/* USER CODE END 3 */
기본 기능이 완성되면 추가 기능을 구현하여 프로젝트를 발전시킬 수 있습니다.
버튼을 길게 누르면 다른 기능을 실행하도록 확장할 수 있습니다.
/* 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 */
전원을 껐다 켜도 마지막 설정을 유지하도록 구현할 수 있습니다.
/* 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 */
다음 주에 배울 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 */
체계적인 테스트로 버그를 사전에 발견하고 수정합니다.
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 */
패턴 전환 테스트
/* 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 */
문제가 발생했을 때 빠르게 원인을 찾는 방법입니다.
문제: 버튼이 반응하지 않음
// 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);
기존 6개 패턴에 자신만의 패턴을 추가해보세요.
요구사항
추천 패턴
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++;
}
4번째 버튼(PA3)을 추가하여 밝기 조절 기능을 구현하세요.
요구사항
힌트: 소프트웨어 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); // 짧은 딜레이
}
}
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;
}
1. 상태 머신 패턴
- 시스템의 상태를 명확히 정의
- 상태 전환 규칙 수립
- 각 상태에서의 동작 정의
- 확장 및 유지보수 용이
2. 코드 구조화
- 데이터 구조체로 관련 정보 묶기
- 기능별 함수 분리
- 인터페이스 명확히 정의
- 재사용 가능한 모듈 작성
3. 디버깅 전략
- UART로 상태 모니터링
- 단위 테스트 작성
- 문제 발생 시 체계적 접근
- 로그 출력으로 흐름 추적
| Day | 주제 | 핵심 내용 |
|---|---|---|
| 1 | 개발환경 | STM32CubeMX, VSCode, OpenOCD |
| 2 | GPIO 출력 | LED 제어, HAL 라이브러리 |
| 3 | GPIO 입력 | 버튼 인터럽트, EXTI |
| 4 | 디바운싱 | 타임스탬프, 상태 머신 방식 |
| 5 | 통합 프로젝트 | 상태 머신, 모듈화, 확장성 |