audio_pwm_hardware.c (4154B)
1 // Copyright 2022 Stefan Kerkmann 2 // Copyright 2020 Jack Humbert 3 // Copyright 2020 JohSchneider 4 // SPDX-License-Identifier: GPL-2.0-or-later 5 6 // Audio Driver: PWM the duty-cycle is always kept at 50%, and the pwm-period is 7 // adjusted to match the frequency of a note to be played back. This driver uses 8 // the chibios-PWM system to produce a square-wave on specific output pins that 9 // are connected to the PWM hardware. The hardware directly toggles the pin via 10 // its alternate function. see your MCUs data-sheet for which pin can be driven 11 // by what timer - looking for TIMx_CHy and the corresponding alternate 12 // function. 13 14 #include "audio.h" 15 #include "gpio.h" 16 17 #if !defined(AUDIO_PIN) 18 # error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings" 19 #endif 20 21 #if !defined(AUDIO_PWM_COUNTER_FREQUENCY) 22 # define AUDIO_PWM_COUNTER_FREQUENCY 100000 23 #endif 24 25 #ifndef AUDIO_PWM_COMPLEMENTARY_OUTPUT 26 # define AUDIO_PWM_OUTPUT_MODE PWM_OUTPUT_ACTIVE_HIGH 27 #else 28 # define AUDIO_PWM_OUTPUT_MODE PWM_COMPLEMENTARY_OUTPUT_ACTIVE_HIGH 29 #endif 30 31 extern bool playing_note; 32 extern bool playing_melody; 33 extern uint8_t note_timbre; 34 35 static PWMConfig pwmCFG = {.frequency = AUDIO_PWM_COUNTER_FREQUENCY, /* PWM clock frequency */ 36 .period = 2, 37 .callback = NULL, 38 .channels = {[(AUDIO_PWM_CHANNEL - 1)] = {.mode = AUDIO_PWM_OUTPUT_MODE, .callback = NULL}}}; 39 40 static float channel_1_frequency = 0.0f; 41 42 void channel_1_set_frequency(float freq) { 43 channel_1_frequency = freq; 44 pwmcnt_t period; 45 pwmcnt_t width; 46 47 if (freq <= 0.0) { 48 period = 2; 49 width = 0; 50 } else { 51 period = (pwmCFG.frequency / freq); 52 width = (pwmcnt_t)(((period) * (pwmcnt_t)((100 - note_timbre) * 100)) / (pwmcnt_t)(10000)); 53 } 54 chSysLockFromISR(); 55 pwmChangePeriodI(&AUDIO_PWM_DRIVER, period); 56 pwmEnableChannelI(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1, width); 57 chSysUnlockFromISR(); 58 } 59 60 float channel_1_get_frequency(void) { 61 return channel_1_frequency; 62 } 63 64 void channel_1_start(void) { 65 pwmStop(&AUDIO_PWM_DRIVER); 66 pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG); 67 } 68 69 void channel_1_stop(void) { 70 pwmStop(&AUDIO_PWM_DRIVER); 71 pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG); 72 pwmEnableChannel(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1, 0); 73 pwmStop(&AUDIO_PWM_DRIVER); 74 } 75 76 static virtual_timer_t audio_vt; 77 static void audio_callback(virtual_timer_t *vtp, void *p); 78 79 // a regular timer task, that checks the note to be currently played and updates 80 // the pwm to output that frequency. 81 static void audio_callback(virtual_timer_t *vtp, void *p) { 82 float freq; // TODO: freq_alt 83 84 if (audio_update_state()) { 85 freq = audio_get_processed_frequency(0); // freq_alt would be index=1 86 channel_1_set_frequency(freq); 87 } 88 89 chSysLockFromISR(); 90 chVTSetI(&audio_vt, TIME_MS2I(16), audio_callback, NULL); 91 chSysUnlockFromISR(); 92 } 93 94 void audio_driver_initialize_impl(void) { 95 pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG); 96 97 // connect the AUDIO_PIN to the PWM hardware 98 #if defined(USE_GPIOV1) // STM32F103C8, RP2040 99 palSetLineMode(AUDIO_PIN, AUDIO_PWM_PAL_MODE); 100 #else // GPIOv2 (or GPIOv3 for f4xx, which is the same/compatible at this command) 101 palSetLineMode(AUDIO_PIN, PAL_MODE_ALTERNATE(AUDIO_PWM_PAL_MODE)); 102 #endif 103 104 chVTObjectInit(&audio_vt); 105 } 106 107 void audio_driver_start_impl(void) { 108 channel_1_stop(); 109 channel_1_start(); 110 111 if ((playing_note || playing_melody) && !chVTIsArmed(&audio_vt)) { 112 // a whole note is one beat, which is - per definition in 113 // musical_notes.h - set to 64 the longest note is 114 // BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4 the tempo (which 115 // might vary!) is in bpm (beats per minute) therefore: if the timer 116 // ticks away at 64Hz (~16.6ms) audio_update_state is called just often 117 // enough to not miss any notes 118 chVTSet(&audio_vt, TIME_MS2I(16), audio_callback, NULL); 119 } 120 } 121 122 void audio_driver_stop_impl(void) { 123 channel_1_stop(); 124 chVTReset(&audio_vt); 125 }