수동 부저 vs 능동 부저
능동 부저 (Active Buzzer)
- 내부에 발진 회로 포함
- 전원만 인가하면 고정된 주파수 출력
- 제어 불가능
- 용도: 알람, 경고음
수동 부저 (Passive Buzzer)
- 외부에서 주파수 신호 입력 필요
- PWM으로 주파수 제어
- 다양한 음 출력 가능
- 용도: 멜로디 재생, 효과음
수동 부저 동작 원리
PWM 신호 입력 → 압전 소자 진동 → 소리 발생
주파수가 높을수록 → 고음
주파수가 낮을수록 → 저음
듀티 사이클 → 음량 제어 (50% 권장)
예시:
440Hz PWM → 라(A4) 음
880Hz PWM → 라(A5) 음 (1옥타브 위)
연결 방법
STM32 PWM 출력 핀 (예: PA8, TIM1_CH1)
↓
[저항 100Ω] (선택사항, 전류 제한)
↓
부저 (+)
↓
부저 (-)
↓
GND
또는 트랜지스터 드라이버 사용:
PWM → [저항 1kΩ] → 트랜지스터 베이스
콜렉터 → 부저 (+)
이미터 → GND
12평균율 음계
서양 음악의 기본: 1옥타브 = 12개 반음
C C# D D# E F F# G G# A A# B
도 도# 레 레# 미 파 파# 솔 솔# 라 라# 시
주파수 계산 공식
기준음: A4 (라) = 440Hz
반음 위 = 현재 주파수 × 2^(1/12)
반음 아래 = 현재 주파수 / 2^(1/12)
1옥타브 위 = 현재 주파수 × 2
1옥타브 아래 = 현재 주파수 / 2
예시:
A4 = 440Hz
A5 = 440Hz × 2 = 880Hz
A3 = 440Hz / 2 = 220Hz
A#4 = 440Hz × 2^(1/12) = 466.16Hz
음계 주파수 테이블 (4옥타브 기준)
// C4(도) ~ B4(시)
const uint16_t notes_freq[] = {
262, // C4 (도)
277, // C#4 (도#)
294, // D4 (레)
311, // D#4 (레#)
330, // E4 (미)
349, // F4 (파)
370, // F#4 (파#)
392, // G4 (솔)
415, // G#4 (솔#)
440, // A4 (라)
466, // A#4 (라#)
494 // B4 (시)
};
음표 길이
온음표 (Whole Note) = 4박자
2분음표 (Half Note) = 2박자
4분음표 (Quarter Note) = 1박자
8분음표 (Eighth Note) = 0.5박자
16분음표 (Sixteenth Note) = 0.25박자
점음표 (Dotted Note) = 원래 길이 × 1.5
예: 점4분음표 = 1박자 × 1.5 = 1.5박자
템포 (BPM)
BPM = Beats Per Minute (1분당 박자 수)
1박자 시간(ms) = 60000 / BPM
예시:
BPM = 120
1박자 = 60000 / 120 = 500ms
4분음표 = 500ms
8분음표 = 250ms
2분음표 = 1000ms
Timer 설정 (TIM1 예시)
1. Pinout & Configuration
- Timers → TIM1 선택
- Clock Source: Internal Clock
- Channel1: PWM Generation CH1
2. Parameter Settings
- Prescaler: 계산 필요 (아래 참조)
- Counter Period (ARR): 계산 필요
- Pulse (CCR): ARR / 2 (50% 듀티)
3. GPIO Settings
- PA8: TIM1_CH1 (부저 연결)
주파수 설정 계산
Timer 주파수 = APB 클럭 / (Prescaler + 1)
출력 주파수 = Timer 주파수 / (ARR + 1)
예시: 440Hz (라) 출력
APB2 클럭 = 168MHz (TIM1은 APB2)
목표 주파수 = 440Hz
방법 1: 높은 분해능
Prescaler = 0
Timer 주파수 = 168MHz
ARR = 168,000,000 / 440 - 1 = 381,817
CCR = ARR / 2 = 190,909
방법 2: 간단한 계산
Prescaler = 168 - 1
Timer 주파수 = 1MHz
ARR = 1,000,000 / 440 - 1 = 2,272
CCR = ARR / 2 = 1,136
초기화 코드
#include "main.h"
extern TIM_HandleTypeDef htim1;
void buzzer_init(void)
{
// PWM 시작 (초기에는 무음)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0); // 무음
}
void buzzer_off(void)
{
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0);
}
주파수 설정 함수
void buzzer_set_frequency(uint16_t frequency)
{
if (frequency == 0)
{
buzzer_off();
return;
}
// Prescaler = 168 - 1 (1MHz Timer)
uint32_t arr = 1000000 / frequency - 1;
uint32_t ccr = arr / 2; // 50% 듀티
__HAL_TIM_SET_AUTORELOAD(&htim1, arr);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, ccr);
}
음 재생 함수
void buzzer_play_tone(uint16_t frequency, uint16_t duration_ms)
{
buzzer_set_frequency(frequency);
HAL_Delay(duration_ms);
buzzer_off();
}
음계 테스트
void test_scale(void)
{
const uint16_t scale[] = {262, 294, 330, 349, 392, 440, 494, 523};
const char* names[] = {"C", "D", "E", "F", "G", "A", "B", "C"};
printf("\r\n=== Scale Test ===\r\n");
for (uint8_t i = 0; i < 8; i++)
{
printf("%s: %d Hz\r\n", names[i], scale[i]);
buzzer_play_tone(scale[i], 500);
HAL_Delay(100); // 음 사이 간격
}
printf("Complete!\r\n");
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM1_Init();
buzzer_init();
printf("\r\n=== Buzzer Test ===\r\n");
test_scale();
while (1)
{
HAL_Delay(1000);
}
}
매크로 정의 방식
// 음표 정의
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_REST 0 // 쉼표
열거형 정의 방식
typedef enum {
NOTE_C,
NOTE_CS,
NOTE_D,
NOTE_DS,
NOTE_E,
NOTE_F,
NOTE_FS,
NOTE_G,
NOTE_GS,
NOTE_A,
NOTE_AS,
NOTE_B,
NOTE_MAX
} Note_t;
typedef enum {
OCTAVE_3 = 0,
OCTAVE_4 = 1,
OCTAVE_5 = 2,
OCTAVE_6 = 3,
OCTAVE_MAX
} Octave_t;
// 주파수 테이블 (옥타브별)
const uint16_t note_freq_table[OCTAVE_MAX][NOTE_MAX] = {
// Octave 3
{131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247},
// Octave 4
{262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494},
// Octave 5
{523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988},
// Octave 6
{1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976}
};
uint16_t get_note_frequency(Note_t note, Octave_t octave)
{
if (note >= NOTE_MAX || octave >= OCTAVE_MAX)
return 0;
return note_freq_table[octave][note];
}
음표 타입
typedef enum {
DURATION_WHOLE = 4, // 온음표
DURATION_HALF = 2, // 2분음표
DURATION_QUARTER = 1, // 4분음표
DURATION_EIGHTH = 0, // 8분음표 (특수 처리)
DURATION_SIXTEENTH = 0 // 16분음표 (특수 처리)
} NoteDuration_t;
// 실제 시간 계산
uint16_t calculate_duration_ms(NoteDuration_t duration, uint16_t bpm)
{
uint16_t quarter_note_ms = 60000 / bpm;
switch (duration)
{
case DURATION_WHOLE:
return quarter_note_ms * 4;
case DURATION_HALF:
return quarter_note_ms * 2;
case DURATION_QUARTER:
return quarter_note_ms;
case DURATION_EIGHTH:
return quarter_note_ms / 2;
case DURATION_SIXTEENTH:
return quarter_note_ms / 4;
default:
return quarter_note_ms;
}
}
확장된 음표 길이
typedef struct {
float beats; // 박자 수 (0.25, 0.5, 1, 1.5, 2, 3, 4 등)
uint8_t is_dotted; // 점음표 여부
} NoteLength_t;
uint16_t get_note_duration_ms(NoteLength_t length, uint16_t bpm)
{
uint16_t quarter_ms = 60000 / bpm;
uint16_t duration = (uint16_t)(quarter_ms * length.beats);
if (length.is_dotted)
{
duration = (uint16_t)(duration * 1.5f);
}
return duration;
}
// 사용 예시
NoteLength_t quarter = {1.0f, 0}; // 4분음표
NoteLength_t dotted_quarter = {1.0f, 1}; // 점4분음표
NoteLength_t eighth = {0.5f, 0}; // 8분음표
NoteLength_t half = {2.0f, 0}; // 2분음표
음표 구조체
typedef struct {
uint16_t frequency; // Hz
uint16_t duration; // ms
} MusicNote_t;
// 학교종 멜로디
const MusicNote_t school_bell[] = {
{NOTE_G4, 500}, {NOTE_G4, 500}, {NOTE_A4, 500}, {NOTE_A4, 500},
{NOTE_G4, 500}, {NOTE_G4, 500}, {NOTE_E4, 1000},
{NOTE_G4, 500}, {NOTE_G4, 500}, {NOTE_E4, 500}, {NOTE_E4, 500},
{NOTE_D4, 1500},
{NOTE_G4, 500}, {NOTE_G4, 500}, {NOTE_A4, 500}, {NOTE_A4, 500},
{NOTE_G4, 500}, {NOTE_G4, 500}, {NOTE_E4, 1000},
{NOTE_G4, 500}, {NOTE_E4, 500}, {NOTE_D4, 500}, {NOTE_E4, 500},
{NOTE_C4, 1500},
{0, 0} // 종료 마커
};
완전한 음표 구조
typedef struct {
Note_t note; // 음계 (C, D, E, ...)
Octave_t octave; // 옥타브 (3, 4, 5, ...)
float beats; // 박자 수
uint8_t is_dotted; // 점음표
uint8_t is_rest; // 쉼표
} AdvancedNote_t;
// 학교종 (BPM 120)
const AdvancedNote_t school_bell_advanced[] = {
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_A, OCTAVE_4, 1.0f, 0, 0}, // 라
{NOTE_A, OCTAVE_4, 1.0f, 0, 0}, // 라
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_E, OCTAVE_4, 2.0f, 0, 0}, // 미 (2박)
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_E, OCTAVE_4, 1.0f, 0, 0}, // 미
{NOTE_E, OCTAVE_4, 1.0f, 0, 0}, // 미
{NOTE_D, OCTAVE_4, 3.0f, 0, 0}, // 레 (3박)
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_A, OCTAVE_4, 1.0f, 0, 0}, // 라
{NOTE_A, OCTAVE_4, 1.0f, 0, 0}, // 라
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_E, OCTAVE_4, 2.0f, 0, 0}, // 미 (2박)
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, // 솔
{NOTE_E, OCTAVE_4, 1.0f, 0, 0}, // 미
{NOTE_D, OCTAVE_4, 1.0f, 0, 0}, // 레
{NOTE_E, OCTAVE_4, 1.0f, 0, 0}, // 미
{NOTE_C, OCTAVE_4, 3.0f, 0, 0}, // 도 (3박)
{NOTE_C, OCTAVE_4, 0.0f, 0, 1} // 종료
};
곡 메타데이터
typedef struct {
const char* title; // 곡 제목
const char* composer; // 작곡가
uint16_t bpm; // 템포
const AdvancedNote_t* notes; // 음표 데이터
uint16_t note_count; // 음표 개수
} Song_t;
// 곡 정의
const Song_t songs[] = {
{
.title = "School Bell",
.composer = "Traditional",
.bpm = 120,
.notes = school_bell_advanced,
.note_count = sizeof(school_bell_advanced) / sizeof(AdvancedNote_t)
},
// 다른 곡들...
};
단순 재생
void play_melody_simple(const MusicNote_t* melody)
{
uint16_t i = 0;
while (melody[i].frequency != 0 || melody[i].duration != 0)
{
if (melody[i].frequency > 0)
{
buzzer_set_frequency(melody[i].frequency);
}
else
{
buzzer_off(); // 쉼표
}
HAL_Delay(melody[i].duration);
buzzer_off();
HAL_Delay(50); // 음 사이 간격
i++;
}
}
// 사용 예시
int main(void)
{
// ...
buzzer_init();
printf("Playing: School Bell\r\n");
play_melody_simple(school_bell);
printf("Complete!\r\n");
while (1)
{
HAL_Delay(1000);
}
}
템포 기반 재생
void play_song(const Song_t* song)
{
printf("\r\n=== Now Playing ===\r\n");
printf("Title: %s\r\n", song->title);
printf("Composer: %s\r\n", song->composer);
printf("BPM: %d\r\n\r\n", song->bpm);
uint16_t quarter_ms = 60000 / song->bpm;
for (uint16_t i = 0; i < song->note_count; i++)
{
const AdvancedNote_t* note = &song->notes[i];
// 종료 체크
if (note->is_rest && note->beats == 0)
break;
// 음 길이 계산
uint16_t duration = (uint16_t)(quarter_ms * note->beats);
if (note->is_dotted)
{
duration = (uint16_t)(duration * 1.5f);
}
// 재생
if (!note->is_rest)
{
uint16_t freq = get_note_frequency(note->note, note->octave);
buzzer_set_frequency(freq);
}
else
{
buzzer_off();
}
// 음 길이의 90%만 소리 (레가토 효과)
HAL_Delay((uint16_t)(duration * 0.9f));
buzzer_off();
HAL_Delay((uint16_t)(duration * 0.1f));
}
buzzer_off();
printf("\r\nPlayback complete!\r\n");
}
타이머 인터럽트 기반
typedef struct {
const Song_t* song;
uint16_t current_note;
uint32_t note_start_time;
uint16_t note_duration;
uint8_t is_playing;
uint8_t is_paused;
} MusicPlayer_t;
volatile MusicPlayer_t player = {0};
void music_player_init(void)
{
player.song = NULL;
player.current_note = 0;
player.is_playing = 0;
player.is_paused = 0;
}
void music_player_start(const Song_t* song)
{
player.song = song;
player.current_note = 0;
player.is_playing = 1;
player.is_paused = 0;
player.note_start_time = HAL_GetTick();
// 첫 음 재생
play_next_note();
}
void play_next_note(void)
{
if (player.song == NULL || !player.is_playing)
return;
if (player.current_note >= player.song->note_count)
{
// 재생 완료
player.is_playing = 0;
buzzer_off();
return;
}
const AdvancedNote_t* note = &player.song->notes[player.current_note];
// 종료 체크
if (note->is_rest && note->beats == 0)
{
player.is_playing = 0;
buzzer_off();
return;
}
// 음 길이 계산
uint16_t quarter_ms = 60000 / player.song->bpm;
player.note_duration = (uint16_t)(quarter_ms * note->beats);
if (note->is_dotted)
{
player.note_duration = (uint16_t)(player.note_duration * 1.5f);
}
// 재생
if (!note->is_rest)
{
uint16_t freq = get_note_frequency(note->note, note->octave);
buzzer_set_frequency(freq);
}
else
{
buzzer_off();
}
player.note_start_time = HAL_GetTick();
}
void music_player_update(void)
{
if (!player.is_playing || player.is_paused)
return;
uint32_t elapsed = HAL_GetTick() - player.note_start_time;
// 음의 90% 지점에서 소리 끄기
if (elapsed >= (uint32_t)(player.note_duration * 0.9f))
{
buzzer_off();
}
// 음 종료
if (elapsed >= player.note_duration)
{
player.current_note++;
play_next_note();
}
}
void music_player_pause(void)
{
player.is_paused = 1;
buzzer_off();
}
void music_player_resume(void)
{
if (player.is_paused)
{
player.is_paused = 0;
player.note_start_time = HAL_GetTick();
}
}
void music_player_stop(void)
{
player.is_playing = 0;
player.is_paused = 0;
buzzer_off();
}
// 메인 루프
int main(void)
{
// ...
buzzer_init();
music_player_init();
music_player_start(&songs[0]);
while (1)
{
music_player_update();
// 다른 작업 가능
HAL_Delay(1);
}
}
PWM 듀티 사이클 제어
typedef enum {
VOLUME_MUTE = 0,
VOLUME_LOW = 25,
VOLUME_MEDIUM = 50,
VOLUME_HIGH = 75,
VOLUME_MAX = 100
} Volume_t;
volatile Volume_t current_volume = VOLUME_MEDIUM;
void buzzer_set_frequency_with_volume(uint16_t frequency, Volume_t volume)
{
if (frequency == 0 || volume == VOLUME_MUTE)
{
buzzer_off();
return;
}
uint32_t arr = 1000000 / frequency - 1;
uint32_t ccr = (arr * volume) / 100;
__HAL_TIM_SET_AUTORELOAD(&htim1, arr);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, ccr);
}
void set_volume(Volume_t volume)
{
current_volume = volume;
}
Volume_t get_volume(void)
{
return current_volume;
}
// 재생 함수에 적용
void play_next_note_with_volume(void)
{
// ... (이전 코드)
if (!note->is_rest)
{
uint16_t freq = get_note_frequency(note->note, note->octave);
buzzer_set_frequency_with_volume(freq, current_volume);
}
// ...
}
배속 재생
typedef enum {
TEMPO_HALF = 50, // 0.5배속
TEMPO_NORMAL = 100, // 1배속
TEMPO_FAST = 150, // 1.5배속
TEMPO_DOUBLE = 200 // 2배속
} TempoMultiplier_t;
volatile TempoMultiplier_t tempo_multiplier = TEMPO_NORMAL;
uint16_t get_adjusted_bpm(uint16_t original_bpm)
{
return (original_bpm * tempo_multiplier) / 100;
}
void set_tempo(TempoMultiplier_t tempo)
{
tempo_multiplier = tempo;
}
// 재생 시 적용
void play_next_note_with_tempo(void)
{
// ...
uint16_t adjusted_bpm = get_adjusted_bpm(player.song->bpm);
uint16_t quarter_ms = 60000 / adjusted_bpm;
player.note_duration = (uint16_t)(quarter_ms * note->beats);
// ...
}
루프 및 구간 반복
typedef struct {
uint8_t enabled;
uint16_t start_note;
uint16_t end_note;
uint16_t repeat_count;
uint16_t current_repeat;
} LoopInfo_t;
volatile LoopInfo_t loop_info = {0};
void set_loop(uint16_t start, uint16_t end, uint16_t count)
{
loop_info.enabled = 1;
loop_info.start_note = start;
loop_info.end_note = end;
loop_info.repeat_count = count;
loop_info.current_repeat = 0;
}
void disable_loop(void)
{
loop_info.enabled = 0;
}
void play_next_note_with_loop(void)
{
// ... (음 재생 로직)
// 루프 체크
if (loop_info.enabled &&
player.current_note >= loop_info.end_note)
{
loop_info.current_repeat++;
if (loop_info.current_repeat < loop_info.repeat_count)
{
player.current_note = loop_info.start_note;
return;
}
else
{
loop_info.enabled = 0;
}
}
// ...
}
// 전체 곡 반복
void set_infinite_loop(void)
{
set_loop(0, player.song->note_count - 1, 0xFFFF);
}
비브라토 (Vibrato)
typedef struct {
uint8_t enabled;
uint16_t rate; // Hz (주파수 변동 속도)
uint16_t depth; // Hz (주파수 변동 폭)
} Vibrato_t;
volatile Vibrato_t vibrato = {
.enabled = 0,
.rate = 5, // 5Hz 변동
.depth = 10 // ±10Hz
};
void apply_vibrato(uint16_t base_freq)
{
if (!vibrato.enabled)
{
buzzer_set_frequency_with_volume(base_freq, current_volume);
return;
}
static uint32_t last_update = 0;
static int16_t offset = 0;
static int8_t direction = 1;
uint32_t now = HAL_GetTick();
if (now - last_update >= (1000 / vibrato.rate / 2))
{
offset += direction * (vibrato.depth / 10);
if (abs(offset) >= vibrato.depth)
{
direction = -direction;
}
last_update = now;
}
uint16_t modulated_freq = base_freq + offset;
buzzer_set_frequency_with_volume(modulated_freq, current_volume);
}
트레몰로 (Tremolo)
typedef struct {
uint8_t enabled;
uint16_t rate; // Hz (음량 변동 속도)
uint8_t depth; // % (음량 변동 폭)
} Tremolo_t;
volatile Tremolo_t tremolo = {
.enabled = 0,
.rate = 5, // 5Hz
.depth = 30 // 30% 변동
};
void apply_tremolo(uint16_t frequency)
{
if (!tremolo.enabled)
{
buzzer_set_frequency_with_volume(frequency, current_volume);
return;
}
static uint32_t last_update = 0;
static int8_t volume_offset = 0;
static int8_t direction = 1;
uint32_t now = HAL_GetTick();
if (now - last_update >= (1000 / tremolo.rate / 2))
{
volume_offset += direction * (tremolo.depth / 10);
if (abs(volume_offset) >= tremolo.depth)
{
direction = -direction;
}
last_update = now;
}
int16_t modulated_volume = current_volume + volume_offset;
if (modulated_volume < 0)
modulated_volume = 0;
if (modulated_volume > 100)
modulated_volume = 100;
buzzer_set_frequency_with_volume(frequency, (Volume_t)modulated_volume);
}
재생 컨트롤
#define BTN_PLAY_PAUSE GPIOA, GPIO_PIN_0
#define BTN_STOP GPIOA, GPIO_PIN_1
#define BTN_NEXT GPIOA, GPIO_PIN_2
#define BTN_PREV GPIOA, GPIO_PIN_3
#define BTN_VOL_UP GPIOA, GPIO_PIN_4
#define BTN_VOL_DOWN GPIOA, GPIO_PIN_5
void handle_button_press(void)
{
static uint32_t last_press[6] = {0};
uint32_t now = HAL_GetTick();
uint32_t debounce = 200; // 200ms 디바운스
// Play/Pause
if (HAL_GPIO_ReadPin(BTN_PLAY_PAUSE) == GPIO_PIN_RESET &&
(now - last_press[0] > debounce))
{
if (player.is_playing && !player.is_paused)
{
music_player_pause();
printf("Paused\r\n");
}
else if (player.is_paused)
{
music_player_resume();
printf("Resumed\r\n");
}
else
{
music_player_start(&songs[0]);
printf("Playing\r\n");
}
last_press[0] = now;
}
// Stop
if (HAL_GPIO_ReadPin(BTN_STOP) == GPIO_PIN_RESET &&
(now - last_press[1] > debounce))
{
music_player_stop();
printf("Stopped\r\n");
last_press[1] = now;
}
// Volume Up
if (HAL_GPIO_ReadPin(BTN_VOL_UP) == GPIO_PIN_RESET &&
(now - last_press[4] > debounce))
{
if (current_volume < VOLUME_MAX)
{
current_volume = (Volume_t)(current_volume + 25);
printf("Volume: %d\r\n", current_volume);
}
last_press[4] = now;
}
// Volume Down
if (HAL_GPIO_ReadPin(BTN_VOL_DOWN) == GPIO_PIN_RESET &&
(now - last_press[5] > debounce))
{
if (current_volume > VOLUME_MUTE)
{
current_volume = (Volume_t)(current_volume - 25);
printf("Volume: %d\r\n", current_volume);
}
last_press[5] = now;
}
}
int main(void)
{
// ...
while (1)
{
music_player_update();
handle_button_press();
HAL_Delay(1);
}
}
재생 정보 표시
void display_player_status(void)
{
static uint32_t last_update = 0;
uint32_t now = HAL_GetTick();
if (now - last_update < 100) // 100ms마다 업데이트
return;
lcd_clear();
if (player.is_playing)
{
// 첫 번째 줄: 곡 제목
lcd_set_cursor(0, 0);
lcd_print(player.song->title);
// 두 번째 줄: 재생 상태
lcd_set_cursor(1, 0);
if (player.is_paused)
{
lcd_print("PAUSED");
}
else
{
char status[16];
uint16_t progress = (player.current_note * 100) / player.song->note_count;
sprintf(status, "Playing %d%%", progress);
lcd_print(status);
}
// 세 번째 줄: 음량
lcd_set_cursor(2, 0);
char vol_str[16];
sprintf(vol_str, "Vol: %d%%", current_volume);
lcd_print(vol_str);
// 네 번째 줄: BPM
lcd_set_cursor(3, 0);
char bpm_str[16];
uint16_t current_bpm = get_adjusted_bpm(player.song->bpm);
sprintf(bpm_str, "BPM: %d", current_bpm);
lcd_print(bpm_str);
}
else
{
lcd_set_cursor(0, 0);
lcd_print("Music Player");
lcd_set_cursor(1, 0);
lcd_print("Press PLAY");
}
last_update = now;
}
헤더 파일
// music_player.h
#ifndef MUSIC_PLAYER_H
#define MUSIC_PLAYER_H
#include "main.h"
// 음계 정의
typedef enum {
NOTE_C, NOTE_CS, NOTE_D, NOTE_DS, NOTE_E, NOTE_F,
NOTE_FS, NOTE_G, NOTE_GS, NOTE_A, NOTE_AS, NOTE_B,
NOTE_MAX
} Note_t;
typedef enum {
OCTAVE_3 = 0, OCTAVE_4, OCTAVE_5, OCTAVE_6, OCTAVE_MAX
} Octave_t;
typedef struct {
Note_t note;
Octave_t octave;
float beats;
uint8_t is_dotted;
uint8_t is_rest;
} AdvancedNote_t;
typedef struct {
const char* title;
const char* composer;
uint16_t bpm;
const AdvancedNote_t* notes;
uint16_t note_count;
} Song_t;
typedef enum {
VOLUME_MUTE = 0, VOLUME_LOW = 25, VOLUME_MEDIUM = 50,
VOLUME_HIGH = 75, VOLUME_MAX = 100
} Volume_t;
// 함수 선언
void music_player_init(void);
void music_player_start(const Song_t* song);
void music_player_pause(void);
void music_player_resume(void);
void music_player_stop(void);
void music_player_update(void);
void set_volume(Volume_t volume);
uint8_t is_playing(void);
#endif
여러 곡 정의
// songs.c
#include "music_player.h"
// 학교종
const AdvancedNote_t school_bell_notes[] = {
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, {NOTE_G, OCTAVE_4, 1.0f, 0, 0},
{NOTE_A, OCTAVE_4, 1.0f, 0, 0}, {NOTE_A, OCTAVE_4, 1.0f, 0, 0},
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, {NOTE_G, OCTAVE_4, 1.0f, 0, 0},
{NOTE_E, OCTAVE_4, 2.0f, 0, 0},
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, {NOTE_G, OCTAVE_4, 1.0f, 0, 0},
{NOTE_E, OCTAVE_4, 1.0f, 0, 0}, {NOTE_E, OCTAVE_4, 1.0f, 0, 0},
{NOTE_D, OCTAVE_4, 3.0f, 0, 0},
{NOTE_C, OCTAVE_4, 0.0f, 0, 1} // 종료
};
// 작은 별
const AdvancedNote_t twinkle_star_notes[] = {
{NOTE_C, OCTAVE_4, 1.0f, 0, 0}, {NOTE_C, OCTAVE_4, 1.0f, 0, 0},
{NOTE_G, OCTAVE_4, 1.0f, 0, 0}, {NOTE_G, OCTAVE_4, 1.0f, 0, 0},
{NOTE_A, OCTAVE_4, 1.0f, 0, 0}, {NOTE_A, OCTAVE_4, 1.0f, 0, 0},
{NOTE_G, OCTAVE_4, 2.0f, 0, 0},
{NOTE_F, OCTAVE_4, 1.0f, 0, 0}, {NOTE_F, OCTAVE_4, 1.0f, 0, 0},
{NOTE_E, OCTAVE_4, 1.0f, 0, 0}, {NOTE_E, OCTAVE_4, 1.0f, 0, 0},
{NOTE_D, OCTAVE_4, 1.0f, 0, 0}, {NOTE_D, OCTAVE_4, 1.0f, 0, 0},
{NOTE_C, OCTAVE_4, 2.0f, 0, 0},
{NOTE_C, OCTAVE_4, 0.0f, 0, 1} // 종료
};
// 생일 축하 노래
const AdvancedNote_t happy_birthday_notes[] = {
{NOTE_G, OCTAVE_4, 0.75f, 0, 0}, {NOTE_G, OCTAVE_4, 0.25f, 0, 0},
{NOTE_A, OCTAVE_4, 1.0f, 0, 0}, {NOTE_G, OCTAVE_4, 1.0f, 0, 0},
{NOTE_C, OCTAVE_5, 1.0f, 0, 0}, {NOTE_B, OCTAVE_4, 2.0f, 0, 0},
{NOTE_G, OCTAVE_4, 0.75f, 0, 0}, {NOTE_G, OCTAVE_4, 0.25f, 0, 0},
{NOTE_A, OCTAVE_4, 1.0f, 0, 0}, {NOTE_G, OCTAVE_4, 1.0f, 0, 0},
{NOTE_D, OCTAVE_5, 1.0f, 0, 0}, {NOTE_C, OCTAVE_5, 2.0f, 0, 0},
{NOTE_G, OCTAVE_4, 0.75f, 0, 0}, {NOTE_G, OCTAVE_4, 0.25f, 0, 0},
{NOTE_G, OCTAVE_5, 1.0f, 0, 0}, {NOTE_E, OCTAVE_5, 1.0f, 0, 0},
{NOTE_C, OCTAVE_5, 1.0f, 0, 0}, {NOTE_B, OCTAVE_4, 1.0f, 0, 0},
{NOTE_A, OCTAVE_4, 2.0f, 0, 0},
{NOTE_F, OCTAVE_5, 0.75f, 0, 0}, {NOTE_F, OCTAVE_5, 0.25f, 0, 0},
{NOTE_E, OCTAVE_5, 1.0f, 0, 0}, {NOTE_C, OCTAVE_5, 1.0f, 0, 0},
{NOTE_D, OCTAVE_5, 1.0f, 0, 0}, {NOTE_C, OCTAVE_5, 2.0f, 0, 0},
{NOTE_C, OCTAVE_4, 0.0f, 0, 1} // 종료
};
// 곡 목록
const Song_t song_list[] = {
{
.title = "School Bell",
.composer = "Traditional",
.bpm = 120,
.notes = school_bell_notes,
.note_count = sizeof(school_bell_notes) / sizeof(AdvancedNote_t)
},
{
.title = "Twinkle Star",
.composer = "Traditional",
.bpm = 120,
.notes = twinkle_star_notes,
.note_count = sizeof(twinkle_star_notes) / sizeof(AdvancedNote_t)
},
{
.title = "Happy Birthday",
.composer = "Traditional",
.bpm = 120,
.notes = happy_birthday_notes,
.note_count = sizeof(happy_birthday_notes) / sizeof(AdvancedNote_t)
}
};
const uint8_t song_count = sizeof(song_list) / sizeof(Song_t);
extern const Song_t song_list[];
extern const uint8_t song_count;
플레이리스트 기능
// main.c
#include "main.h"
#include "music_player.h"
#include "songs.h"
#include "stdio.h"
uint8_t current_song_index = 0;
void play_next_song(void)
{
current_song_index++;
if (current_song_index >= song_count)
{
current_song_index = 0;
}
music_player_start(&song_list[current_song_index]);
printf("\r\n=== Now Playing ===\r\n");
printf("Song %d/%d: %s\r\n",
current_song_index + 1,
song_count,
song_list[current_song_index].title);
}
void play_previous_song(void)
{
if (current_song_index == 0)
{
current_song_index = song_count - 1;
}
else
{
current_song_index--;
}
music_player_start(&song_list[current_song_index]);
printf("\r\n=== Now Playing ===\r\n");
printf("Song %d/%d: %s\r\n",
current_song_index + 1,
song_count,
song_list[current_song_index].title);
}
void show_playlist(void)
{
printf("\r\n=== Playlist ===\r\n");
for (uint8_t i = 0; i < song_count; i++)
{
if (i == current_song_index)
{
printf("> ");
}
else
{
printf(" ");
}
printf("%d. %s (%s) - %d BPM\r\n",
i + 1,
song_list[i].title,
song_list[i].composer,
song_list[i].bpm);
}
printf("\r\n");
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM1_Init();
printf("\r\n=== Music Player System ===\r\n");
printf("Total songs: %d\r\n", song_count);
music_player_init();
show_playlist();
printf("\r\nCommands:\r\n");
printf("1: Play/Pause\r\n");
printf("2: Stop\r\n");
printf("3: Next song\r\n");
printf("4: Previous song\r\n");
printf("5: Volume up\r\n");
printf("6: Volume down\r\n");
printf("7: Show playlist\r\n\r\n");
while (1)
{
music_player_update();
handle_button_press();
// 곡 종료 시 다음 곡 자동 재생
static uint8_t was_playing = 0;
if (was_playing && !is_playing())
{
HAL_Delay(500); // 0.5초 대기
play_next_song();
}
was_playing = is_playing();
HAL_Delay(1);
}
}
PROGMEM 사용 (플래시 메모리 저장)
// 곡 데이터를 플래시에 저장하여 RAM 절약
const AdvancedNote_t school_bell_notes[] __attribute__((section(".rodata"))) = {
// ...
};
// 읽기
AdvancedNote_t note;
memcpy(¬e, &school_bell_notes[i], sizeof(AdvancedNote_t));
타이머 DMA 사용
// CCR 값을 DMA로 자동 업데이트하여 정확한 타이밍 확보
uint16_t frequency_buffer[100];
void setup_pwm_dma(void)
{
// DMA 설정으로 주파수 변경 시 지터 최소화
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1,
(uint32_t*)frequency_buffer, 100);
}
재생 상태 모니터링
void debug_print_player_status(void)
{
if (!player.is_playing)
return;
printf("Note: %d/%d | Time: %lu/%lu ms | Vol: %d%%\r\n",
player.current_note,
player.song->note_count,
HAL_GetTick() - player.note_start_time,
player.note_duration,
current_volume);
}
사용자가 버튼으로 음을 입력하여 멜로디를 작곡하고 재생하는 시스템을 구현하세요.
요구사항
힌트
typedef struct {
AdvancedNote_t notes[100];
uint16_t note_count;
} UserSong_t;
void record_note(Note_t note, Octave_t octave);
void save_to_eeprom(void);
void load_from_eeprom(void);
RTTTL(Ring Tone Text Transfer Language) 형식의 멜로디를 파싱하여 재생하세요.
요구사항
RTTTL 예시
Super Mario:d=4,o=5,b=125:16e6,16e6,32p,8e6,16c6,8e6,8g6,8p,8g
힌트
void parse_rtttl(const char* rtttl, Song_t* song);
uint16_t parse_note(const char* note_str);
uint8_t parse_duration(const char* dur_str);
LCD에 재생 중인 음표를 실시간으로 시각화하세요.
요구사항
힌트
void draw_note_on_lcd(Note_t note, Octave_t octave, uint8_t x);
void draw_spectrum(uint16_t frequency);
void animate_beat(void);
증상
부저에서 아무 소리도 나지 않음
진단 및 해결
// 1. PWM 시작 확인
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
// 2. CCR 값 확인
uint32_t ccr = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_1);
printf("CCR: %lu\r\n", ccr); // 0이면 문제
// 3. 핀 설정 확인
// PA8 = TIM1_CH1 (AF1)
// 4. 부저 극성 확인
// (+)와 (-) 반대로 연결했을 수 있음
// 5. 주파수 범위 확인
// 인간 가청 주파수: 20Hz ~ 20kHz
// 부저 권장: 200Hz ~ 5kHz
증상
음계가 맞지 않음, 음이 높거나 낮음
해결
// 원인: Prescaler 설정 오류
// APB2 클럭 확인 (TIM1은 APB2)
// SystemCoreClock, APB2 클럭 확인
// 계산 재확인
// Timer 주파수 = APB2 클럭 / (PSC + 1)
// 출력 주파수 = Timer 주파수 / (ARR + 1)
// 예시: 440Hz 목표
// APB2 = 168MHz, PSC = 167
// Timer = 168MHz / 168 = 1MHz
// ARR = 1MHz / 440Hz - 1 = 2272
void verify_frequency(void)
{
uint32_t psc = __HAL_TIM_GET_PRESCALER(&htim1);
uint32_t arr = __HAL_TIM_GET_AUTORELOAD(&htim1);
uint32_t timer_freq = 168000000 / (psc + 1);
uint32_t output_freq = timer_freq / (arr + 1);
printf("PSC: %lu, ARR: %lu\r\n", psc, arr);
printf("Output: %lu Hz\r\n", output_freq);
}
증상
음이 바뀔 때 클릭 소리 발생
해결
// 원인: PWM 값이 급격히 변함
// 해결: 페이드 효과
void buzzer_fade_to_frequency(uint16_t new_freq, uint16_t fade_ms)
{
uint32_t old_arr = __HAL_TIM_GET_AUTORELOAD(&htim1);
uint32_t new_arr = 1000000 / new_freq - 1;
int32_t step = (int32_t)(new_arr - old_arr) / 10;
for (int i = 0; i < 10; i++)
{
old_arr += step;
__HAL_TIM_SET_AUTORELOAD(&htim1, old_arr);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, old_arr / 2);
HAL_Delay(fade_ms / 10);
}
}
// 또는 음 사이 짧은 무음 삽입
buzzer_off();
HAL_Delay(10);
buzzer_set_frequency(next_freq);
증상
BPM 120인데 실제로는 더 빠르거나 느림
해결
// 원인: HAL_Delay() 오차 누적
// 해결: 절대 시간 기준 사용
void play_with_accurate_timing(void)
{
uint32_t start_time = HAL_GetTick();
uint16_t quarter_ms = 60000 / player.song->bpm;
for (uint16_t i = 0; i < player.song->note_count; i++)
{
const AdvancedNote_t* note = &player.song->notes[i];
// 이 음이 시작해야 할 절대 시간
uint32_t target_time = start_time + (i * quarter_ms * note->beats);
// 현재 시간
uint32_t now = HAL_GetTick();
// 대기
if (target_time > now)
{
HAL_Delay(target_time - now);
}
// 재생
if (!note->is_rest)
{
uint16_t freq = get_note_frequency(note->note, note->octave);
buzzer_set_frequency(freq);
}
}
}
부저 제어 기본
멜로디 재생
고급 기능
시스템 구현
1. 주파수 설정
ARR = Timer_Freq / Output_Freq - 1
CCR = ARR / 2 // 50% 듀티
2. 음 길이 계산
duration_ms = (60000 / BPM) * beats
3. 비블로킹 재생
// HAL_GetTick() 기반 시간 관리
if (elapsed >= duration) play_next_note();
| 음계 | 4옥타브 | 5옥타브 | 6옥타브 |
|---|---|---|---|
| C (도) | 262 Hz | 523 Hz | 1047 Hz |
| D (레) | 294 Hz | 587 Hz | 1175 Hz |
| E (미) | 330 Hz | 659 Hz | 1319 Hz |
| F (파) | 349 Hz | 698 Hz | 1397 Hz |
| G (솔) | 392 Hz | 784 Hz | 1568 Hz |
| A (라) | 440 Hz | 880 Hz | 1760 Hz |
| B (시) | 494 Hz | 988 Hz | 1976 Hz |