/* * Sound.c * * Created on: Oct 1, 2024 * Author: DashyFox */ #include "Sound.h" #include "stm32f103xb.h" #include "SimpleTimer.h" uint32_t bpm = 120; // Default BPM static uint32_t note_end_time_ch[4] = {0, 0, 0, 0}; // End times for each channel (1-4) static Melody_t melodies[4]; // Array of melodies for each channel // Initialize the sound system #define SOUND_TIMER TIM4 #define SOUND_TIMER_RCC RCC_APB1ENR_TIM4EN // Initialize the sound system void sound_init(void) { // Dont need, conflict with uart // // Enable timer clock // RCC->APB1ENR |= SOUND_TIMER_RCC; // // // Configure GPIO pins for timer output (assuming GPIOB pins for TIM4 CH3 and CH4) // RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // // // Configure only channels 3 and 4 // // Channel 3 // GPIOB->CRH &= ~(0xF << (3 * 4)); // Clear configuration for PB10 (TIM4_CH3) // GPIOB->CRH |= (0xB << (3 * 4)); // Set to alternate function push-pull // // // Channel 4 // GPIOB->CRH &= ~(0xF << (4 * 4)); // Clear configuration for PB11 (TIM4_CH4) // GPIOB->CRH |= (0xB << (4 * 4)); // Set to alternate function push-pull // // // Initialize the timer for PWM output // SOUND_TIMER->PSC = 0; // SOUND_TIMER->ARR = 0xFFFF; // // // Configure only channels 3 and 4 for PWM output // SOUND_TIMER->CCMR2 &= ~(TIM_CCMR2_OC3M | TIM_CCMR2_OC4M); // Clear channel 3 and 4 mode // SOUND_TIMER->CCMR2 |= (6 << TIM_CCMR2_OC3M_Pos); // Set channel 3 to PWM mode 1 // SOUND_TIMER->CCER |= (TIM_CCER_CC3E | TIM_CCER_CC4E); // Enable output for channels 3 and 4 // // SOUND_TIMER->EGR = TIM_EGR_UG; // Update the timer // SOUND_TIMER->CR1 |= TIM_CR1_CEN; // Enable the timer } // Set BPM void sound_set_bpm(uint32_t new_bpm) { bpm = new_bpm; } // Play a note on a specific channel void sound_play_note(Note_t note, uint8_t channel) { if (channel < 1 || channel > 4) return; uint32_t *ccr = &SOUND_TIMER->CCR1 + (channel - 1); // Select correct CCR for the channel uint32_t *ccmr = (channel <= 2) ? &SOUND_TIMER->CCMR1 : &SOUND_TIMER->CCMR2; uint32_t *ccer = &SOUND_TIMER->CCER; uint32_t enable_cc = TIM_CCER_CC1E << ((channel - 1) * 4); uint32_t duration = (note.duration == T0) ? 0 : (60000 * 4 / (bpm * note.duration)); if (note.frequency == 0) { *ccer &= ~enable_cc; note_end_time_ch[channel - 1] = millis() + duration; } else { // Если текущая нота отличается от новой, отключите старую if (*ccer & enable_cc) { *ccer &= ~enable_cc; // Отключить старую ноту } uint32_t timer_clock = SystemCoreClock / 2; uint32_t prescaler = (timer_clock / (note.frequency * 65536)) + 1; uint32_t arr = (timer_clock / (prescaler * note.frequency)) - 1; uint32_t ccr_val = ((arr + 1) * note.duty_cycle) / 100 - 1; SOUND_TIMER->PSC = prescaler - 1; SOUND_TIMER->ARR = arr; *ccr = ccr_val; *ccmr &= ~(TIM_CCMR1_OC1M << ((channel - 1) * 8)); *ccmr |= (6 << TIM_CCMR1_OC1M_Pos) << ((channel - 1) * 8); *ccer |= enable_cc; SOUND_TIMER->EGR = TIM_EGR_UG; SOUND_TIMER->CR1 |= TIM_CR1_CEN; note_end_time_ch[channel - 1] = (duration == 0) ? 0 : millis() + duration; } } static void play_next_note(uint8_t channel); // Play a melody on a specific channel with repeat count void sound_play_melody(Note_t* melody, uint32_t size, uint8_t channel, uint32_t repeat_count) { if (channel < 1 || channel > 4 || melody == NULL || size == 0) return; uint8_t ch_idx = channel - 1; // Если уже есть мелодия, которую нужно перебить if (melodies[ch_idx].notes != NULL) { // Остановите текущую мелодию SOUND_TIMER->CCER &= ~(TIM_CCER_CC1E << (ch_idx * 4)); // Отключить выход melodies[ch_idx].notes = NULL; // Удалить текущую мелодию } melodies[ch_idx].notes = melody; melodies[ch_idx].size = size; melodies[ch_idx].current_note = 0; melodies[ch_idx].repeat_count = repeat_count; melodies[ch_idx].repeat_left = repeat_count; play_next_note(channel); // sound_play_note(melody[0], channel); // Play the first note } // Function to play the next note in the melody for a specific channel void play_next_note(uint8_t channel) { uint8_t ch_idx = channel - 1; Melody_t* melody = &melodies[ch_idx]; if (melody->current_note >= melody->size) { // If end of melody, check repeat if (melody->repeat_left > 0) { melody->repeat_left--; melody->current_note = 0; // Restart melody } else { // Melody finished, no more repeats melody->notes = NULL; return; } } sound_play_note(melody->notes[melody->current_note], channel); melody->current_note++; } // Function to be called periodically to handle note duration void sound_tick(void) { for (uint8_t ch = 0; ch < 4; ++ch) { if (note_end_time_ch[ch] != 0 && millis() >= note_end_time_ch[ch]) { SOUND_TIMER->CCER &= ~(TIM_CCER_CC1E << (ch * 4)); // Disable output note_end_time_ch[ch] = 0; if (melodies[ch].notes != NULL) { play_next_note(ch + 1); // Play next note for channel } } } } // Create a note Note_t sound_create_note(MusicalNote_t note, NoteDuration_t duration, uint8_t duty_cycle) { return (Note_t){ .frequency = note, .duration = duration, .duty_cycle = duty_cycle }; }