#include "buzzer.h" #include "stm32f7xx_hal.h" #include "config.h" #include /* ================================================================ * Buzzer Hardware Configuration * ================================================================ */ #define BUZZER_PIN GPIO_PIN_8 #define BUZZER_PORT GPIOA #define BUZZER_TIM TIM1 #define BUZZER_TIM_CHANNEL TIM_CHANNEL_1 #define BUZZER_BASE_FREQ_HZ 1000 /* Base PWM frequency (1kHz) */ /* ================================================================ * Predefined Melodies * ================================================================ */ /* Startup jingle: C-E-G ascending pattern */ const MelodyNote melody_startup[] = { {NOTE_C4, DURATION_QUARTER}, {NOTE_E4, DURATION_QUARTER}, {NOTE_G4, DURATION_QUARTER}, {NOTE_C5, DURATION_HALF}, {NOTE_REST, 0} /* Terminator */ }; /* Low battery warning: two descending beeps */ const MelodyNote melody_low_battery[] = { {NOTE_A5, DURATION_EIGHTH}, {NOTE_REST, DURATION_EIGHTH}, {NOTE_A5, DURATION_EIGHTH}, {NOTE_REST, DURATION_EIGHTH}, {NOTE_F5, DURATION_EIGHTH}, {NOTE_REST, DURATION_EIGHTH}, {NOTE_F5, DURATION_EIGHTH}, {NOTE_REST, 0} }; /* Error alert: rapid repeating tone */ const MelodyNote melody_error[] = { {NOTE_E5, DURATION_SIXTEENTH}, {NOTE_REST, DURATION_SIXTEENTH}, {NOTE_E5, DURATION_SIXTEENTH}, {NOTE_REST, DURATION_SIXTEENTH}, {NOTE_E5, DURATION_SIXTEENTH}, {NOTE_REST, DURATION_SIXTEENTH}, {NOTE_REST, 0} }; /* Docking complete: cheerful ascending chime */ const MelodyNote melody_docking_complete[] = { {NOTE_C4, DURATION_EIGHTH}, {NOTE_E4, DURATION_EIGHTH}, {NOTE_G4, DURATION_EIGHTH}, {NOTE_C5, DURATION_QUARTER}, {NOTE_REST, DURATION_QUARTER}, {NOTE_G4, DURATION_EIGHTH}, {NOTE_C5, DURATION_HALF}, {NOTE_REST, 0} }; /* ================================================================ * Melody Queue * ================================================================ */ #define MELODY_QUEUE_SIZE 4 typedef struct { const MelodyNote *notes; /* Melody sequence pointer */ uint16_t note_index; /* Current note in sequence */ uint32_t note_start_ms; /* When current note started */ uint32_t note_duration_ms; /* Duration of current note */ uint16_t current_frequency; /* Current tone frequency (Hz) */ bool is_custom; /* Is this a custom melody? */ } MelodyPlayback; typedef struct { MelodyPlayback queue[MELODY_QUEUE_SIZE]; uint8_t write_index; uint8_t read_index; uint8_t count; } MelodyQueue; static MelodyQueue s_queue = {0}; static MelodyPlayback s_current = {0}; static uint32_t s_last_tick_ms = 0; /* ================================================================ * Hardware Initialization * ================================================================ */ void buzzer_init(void) { /* Enable GPIO and timer clocks */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_TIM1_CLK_ENABLE(); /* Configure PA8 as TIM1_CH1 PWM output */ GPIO_InitTypeDef gpio_init = {0}; gpio_init.Pin = BUZZER_PIN; gpio_init.Mode = GPIO_MODE_AF_PP; gpio_init.Pull = GPIO_NOPULL; gpio_init.Speed = GPIO_SPEED_HIGH; gpio_init.Alternate = GPIO_AF1_TIM1; HAL_GPIO_Init(BUZZER_PORT, &gpio_init); /* Configure TIM1 for PWM: * Clock: 216MHz / PSC = output frequency * For 1kHz base frequency: PSC = 216, ARR = 1000 * Duty cycle = CCR / ARR (e.g., 500/1000 = 50%) */ TIM_HandleTypeDef htim1 = {0}; htim1.Instance = BUZZER_TIM; htim1.Init.Prescaler = 216 - 1; /* 216MHz / 216 = 1MHz clock */ htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = (1000000 / BUZZER_BASE_FREQ_HZ) - 1; /* 1kHz = 1000 counts */ htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; HAL_TIM_PWM_Init(&htim1); /* Configure PWM on CH1: 50% duty cycle initially (silence will be 0%) */ TIM_OC_InitTypeDef oc_init = {0}; oc_init.OCMode = TIM_OCMODE_PWM1; oc_init.Pulse = 0; /* Start at 0% duty (silence) */ oc_init.OCPolarity = TIM_OCPOLARITY_HIGH; oc_init.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim1, &oc_init, BUZZER_TIM_CHANNEL); /* Start PWM generation */ HAL_TIM_PWM_Start(BUZZER_TIM, BUZZER_TIM_CHANNEL); /* Initialize queue */ memset(&s_queue, 0, sizeof(s_queue)); memset(&s_current, 0, sizeof(s_current)); s_last_tick_ms = 0; } /* ================================================================ * Public API * ================================================================ */ bool buzzer_play_melody(MelodyType melody_type) { const MelodyNote *notes = NULL; switch (melody_type) { case MELODY_STARTUP: notes = melody_startup; break; case MELODY_LOW_BATTERY: notes = melody_low_battery; break; case MELODY_ERROR: notes = melody_error; break; case MELODY_DOCKING_COMPLETE: notes = melody_docking_complete; break; default: return false; } return buzzer_play_custom(notes); } bool buzzer_play_custom(const MelodyNote *notes) { if (!notes || s_queue.count >= MELODY_QUEUE_SIZE) { return false; } MelodyPlayback *playback = &s_queue.queue[s_queue.write_index]; memset(playback, 0, sizeof(*playback)); playback->notes = notes; playback->note_index = 0; playback->is_custom = true; s_queue.write_index = (s_queue.write_index + 1) % MELODY_QUEUE_SIZE; s_queue.count++; return true; } bool buzzer_play_tone(uint16_t frequency, uint16_t duration_ms) { if (s_queue.count >= MELODY_QUEUE_SIZE) { return false; } /* Create a simple 2-note melody: tone + rest */ static MelodyNote temp_notes[3]; temp_notes[0].frequency = frequency; temp_notes[0].duration_ms = duration_ms; temp_notes[1].frequency = NOTE_REST; temp_notes[1].duration_ms = 0; MelodyPlayback *playback = &s_queue.queue[s_queue.write_index]; memset(playback, 0, sizeof(*playback)); playback->notes = temp_notes; playback->note_index = 0; playback->is_custom = true; s_queue.write_index = (s_queue.write_index + 1) % MELODY_QUEUE_SIZE; s_queue.count++; return true; } void buzzer_stop(void) { /* Clear queue and current playback */ memset(&s_queue, 0, sizeof(s_queue)); memset(&s_current, 0, sizeof(s_current)); /* Silence buzzer (0% duty cycle) */ TIM1->CCR1 = 0; } bool buzzer_is_playing(void) { return (s_current.notes != NULL) || (s_queue.count > 0); } /* ================================================================ * Timer Update and PWM Frequency Control * ================================================================ */ static void buzzer_set_frequency(uint16_t frequency) { if (frequency == 0) { /* Silence: 0% duty cycle */ TIM1->CCR1 = 0; return; } /* Set PWM frequency and 50% duty cycle * TIM1 clock: 1MHz (after prescaler) * ARR = 1MHz / frequency * CCR1 = ARR / 2 (50% duty) */ uint32_t arr = (1000000 / frequency); if (arr > 65535) arr = 65535; /* Clamp to 16-bit */ TIM1->ARR = arr - 1; TIM1->CCR1 = arr / 2; /* 50% duty cycle for all tones */ } void buzzer_tick(uint32_t now_ms) { /* Check if current note has finished */ if (s_current.notes != NULL) { uint32_t elapsed = now_ms - s_current.note_start_ms; if (elapsed >= s_current.note_duration_ms) { /* Move to next note */ s_current.note_index++; if (s_current.notes[s_current.note_index].duration_ms == 0) { /* End of melody sequence */ s_current.notes = NULL; buzzer_set_frequency(0); /* Start next queued melody if available */ if (s_queue.count > 0) { s_current = s_queue.queue[s_queue.read_index]; s_queue.read_index = (s_queue.read_index + 1) % MELODY_QUEUE_SIZE; s_queue.count--; s_current.note_start_ms = now_ms; s_current.note_duration_ms = s_current.notes[0].duration_ms; buzzer_set_frequency(s_current.notes[0].frequency); } } else { /* Play next note */ s_current.note_start_ms = now_ms; s_current.note_duration_ms = s_current.notes[s_current.note_index].duration_ms; uint16_t frequency = s_current.notes[s_current.note_index].frequency; buzzer_set_frequency(frequency); } } } else if (s_queue.count > 0 && s_current.notes == NULL) { /* Start first queued melody */ s_current = s_queue.queue[s_queue.read_index]; s_queue.read_index = (s_queue.read_index + 1) % MELODY_QUEUE_SIZE; s_queue.count--; s_current.note_start_ms = now_ms; s_current.note_duration_ms = s_current.notes[0].duration_ms; buzzer_set_frequency(s_current.notes[0].frequency); } s_last_tick_ms = now_ms; }