audio_dac_basic.c (9403B)
1 /* Copyright 2016-2020 Jack Humbert 2 * Copyright 2020 JohSchneider 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "audio.h" 19 #include "gpio.h" 20 21 // Need to disable GCC's "tautological-compare" warning for this file, as it causes issues when running `KEEP_INTERMEDIATES=yes`. Corresponding pop at the end of the file. 22 #pragma GCC diagnostic push 23 #pragma GCC diagnostic ignored "-Wtautological-compare" 24 25 /* 26 Audio Driver: DAC 27 28 which utilizes both channels of the DAC unit many STM32 are equipped with to output a modulated square-wave, from precomputed samples stored in a buffer, which is passed to the hardware through DMA 29 30 this driver can either be used to drive to separate speakers, wired to A4+Gnd and A5+Gnd, which allows two tones to be played simultaneously 31 OR 32 one speaker wired to A4+A5 with the AUDIO_PIN_ALT_AS_NEGATIVE define set - see docs/feature_audio 33 34 */ 35 36 #if !defined(AUDIO_PIN) 37 # pragma message "Audio feature enabled, but no suitable pin selected as AUDIO_PIN - see docs/feature_audio under 'ARM (DAC basic)' for available options." 38 // TODO: make this an 'error' instead; go through a breaking change, and add AUDIO_PIN A5 to all keyboards currently using AUDIO on STM32 based boards? - for now: set the define here 39 # define AUDIO_PIN A5 40 #endif 41 // check configuration for ONE speaker, connected to both DAC pins 42 #if defined(AUDIO_PIN_ALT_AS_NEGATIVE) && !defined(AUDIO_PIN_ALT) 43 # error "Audio feature: AUDIO_PIN_ALT_AS_NEGATIVE set, but no pin configured as AUDIO_PIN_ALT" 44 #endif 45 46 #ifndef AUDIO_PIN_ALT 47 // no ALT pin defined is valid, but the c-ifs below need some value set 48 # define AUDIO_PIN_ALT -1 49 #endif 50 51 #if !defined(AUDIO_STATE_TIMER) 52 # define AUDIO_STATE_TIMER GPTD8 53 #endif 54 55 // square-wave 56 static const dacsample_t dac_buffer_1[AUDIO_DAC_BUFFER_SIZE] = { 57 // First half is max, second half is 0 58 [0 ... AUDIO_DAC_BUFFER_SIZE / 2 - 1] = AUDIO_DAC_SAMPLE_MAX, 59 [AUDIO_DAC_BUFFER_SIZE / 2 ... AUDIO_DAC_BUFFER_SIZE - 1] = 0, 60 }; 61 62 // square-wave 63 static const dacsample_t dac_buffer_2[AUDIO_DAC_BUFFER_SIZE] = { 64 // opposite of dac_buffer above 65 [0 ... AUDIO_DAC_BUFFER_SIZE / 2 - 1] = 0, 66 [AUDIO_DAC_BUFFER_SIZE / 2 ... AUDIO_DAC_BUFFER_SIZE - 1] = AUDIO_DAC_SAMPLE_MAX, 67 }; 68 69 GPTConfig gpt6cfg1 = {.frequency = AUDIO_DAC_SAMPLE_RATE, 70 .callback = NULL, 71 .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ 72 .dier = 0U}; 73 GPTConfig gpt7cfg1 = {.frequency = AUDIO_DAC_SAMPLE_RATE, 74 .callback = NULL, 75 .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ 76 .dier = 0U}; 77 78 static void gpt_audio_state_cb(GPTDriver *gptp); 79 GPTConfig gptStateUpdateCfg = {.frequency = 10, 80 .callback = gpt_audio_state_cb, 81 .cr2 = TIM_CR2_MMS_1, /* MMS = 010 = TRGO on Update Event. */ 82 .dier = 0U}; 83 84 static const DACConfig dac_conf_ch1 = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_DHRM_12BIT_RIGHT}; 85 static const DACConfig dac_conf_ch2 = {.init = AUDIO_DAC_OFF_VALUE, .datamode = DAC_DHRM_12BIT_RIGHT}; 86 87 /** 88 * @note The DAC_TRG(0) here selects the Timer 6 TRGO event, which is triggered 89 * on the rising edge after 3 APB1 clock cycles, causing our gpt6cfg1.frequency 90 * to be a third of what we expect. 91 * 92 * Here are all the values for DAC_TRG (TSEL in the ref manual) 93 * TIM15_TRGO 0b011 94 * TIM2_TRGO 0b100 95 * TIM3_TRGO 0b001 96 * TIM6_TRGO 0b000 97 * TIM7_TRGO 0b010 98 * EXTI9 0b110 99 * SWTRIG 0b111 100 */ 101 static const DACConversionGroup dac_conv_grp_ch1 = {.num_channels = 1U, .trigger = DAC_TRG(0b000)}; 102 static const DACConversionGroup dac_conv_grp_ch2 = {.num_channels = 1U, .trigger = DAC_TRG(0b010)}; 103 104 void channel_1_start(void) { 105 gptStart(&GPTD6, &gpt6cfg1); 106 gptStartContinuous(&GPTD6, 2U); 107 palSetPadMode(GPIOA, 4, PAL_MODE_INPUT_ANALOG); 108 } 109 110 void channel_1_stop(void) { 111 gptStopTimer(&GPTD6); 112 palSetPadMode(GPIOA, 4, PAL_MODE_OUTPUT_PUSHPULL); 113 palSetPad(GPIOA, 4); 114 } 115 116 static float channel_1_frequency = 0.0f; 117 void channel_1_set_frequency(float freq) { 118 channel_1_frequency = freq; 119 120 channel_1_stop(); 121 if (freq <= 0.0) // a pause/rest has freq=0 122 return; 123 124 gpt6cfg1.frequency = 2 * freq * AUDIO_DAC_BUFFER_SIZE; 125 channel_1_start(); 126 } 127 float channel_1_get_frequency(void) { 128 return channel_1_frequency; 129 } 130 131 void channel_2_start(void) { 132 gptStart(&GPTD7, &gpt7cfg1); 133 gptStartContinuous(&GPTD7, 2U); 134 palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG); 135 } 136 137 void channel_2_stop(void) { 138 gptStopTimer(&GPTD7); 139 palSetPadMode(GPIOA, 5, PAL_MODE_OUTPUT_PUSHPULL); 140 palSetPad(GPIOA, 5); 141 } 142 143 static float channel_2_frequency = 0.0f; 144 void channel_2_set_frequency(float freq) { 145 channel_2_frequency = freq; 146 147 channel_2_stop(); 148 if (freq <= 0.0) // a pause/rest has freq=0 149 return; 150 151 gpt7cfg1.frequency = 2 * freq * AUDIO_DAC_BUFFER_SIZE; 152 channel_2_start(); 153 } 154 float channel_2_get_frequency(void) { 155 return channel_2_frequency; 156 } 157 158 static void gpt_audio_state_cb(GPTDriver *gptp) { 159 if (audio_update_state()) { 160 #if defined(AUDIO_PIN_ALT_AS_NEGATIVE) 161 // one piezo/speaker connected to both audio pins, the generated square-waves are inverted 162 channel_1_set_frequency(audio_get_processed_frequency(0)); 163 channel_2_set_frequency(audio_get_processed_frequency(0)); 164 165 #else // two separate audio outputs/speakers 166 // primary speaker on A4, optional secondary on A5 167 if (AUDIO_PIN == A4) { 168 channel_1_set_frequency(audio_get_processed_frequency(0)); 169 if (AUDIO_PIN_ALT == A5) { 170 if (audio_get_number_of_active_tones() > 1) { 171 channel_2_set_frequency(audio_get_processed_frequency(1)); 172 } else { 173 channel_2_stop(); 174 } 175 } 176 } 177 178 // primary speaker on A5, optional secondary on A4 179 if (AUDIO_PIN == A5) { 180 channel_2_set_frequency(audio_get_processed_frequency(0)); 181 if (AUDIO_PIN_ALT == A4) { 182 if (audio_get_number_of_active_tones() > 1) { 183 channel_1_set_frequency(audio_get_processed_frequency(1)); 184 } else { 185 channel_1_stop(); 186 } 187 } 188 } 189 #endif 190 } 191 } 192 193 void audio_driver_initialize_impl(void) { 194 if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) { 195 palSetPadMode(GPIOA, 4, PAL_MODE_INPUT_ANALOG); 196 dacStart(&DACD1, &dac_conf_ch1); 197 198 // initial setup of the dac-triggering timer is still required, even 199 // though it gets reconfigured and restarted later on 200 gptStart(&GPTD6, &gpt6cfg1); 201 } 202 203 if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) { 204 palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG); 205 dacStart(&DACD2, &dac_conf_ch2); 206 207 gptStart(&GPTD7, &gpt7cfg1); 208 } 209 210 /* enable the output buffer, to directly drive external loads with no additional circuitry 211 * 212 * see: AN4566 Application note: Extending the DAC performance of STM32 microcontrollers 213 * Note: Buffer-Off bit -> has to be set 0 to enable the output buffer 214 * Note: enabling the output buffer imparts an additional dc-offset of a couple mV 215 * 216 * this is done here, reaching directly into the stm32 registers since chibios has not implemented BOFF handling yet 217 * (see: chibios/os/hal/ports/STM32/todo.txt '- BOFF handling in DACv1.' 218 */ 219 DACD1.params->dac->CR &= ~DAC_CR_BOFF1; 220 DACD2.params->dac->CR &= ~DAC_CR_BOFF2; 221 222 // start state-updater 223 gptStart(&AUDIO_STATE_TIMER, &gptStateUpdateCfg); 224 } 225 226 void audio_driver_stop_impl(void) { 227 if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) { 228 gptStopTimer(&GPTD6); 229 230 // stop the ongoing conversion and put the output in a known state 231 dacStopConversion(&DACD1); 232 dacPutChannelX(&DACD1, 0, AUDIO_DAC_OFF_VALUE); 233 } 234 235 if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) { 236 gptStopTimer(&GPTD7); 237 238 dacStopConversion(&DACD2); 239 dacPutChannelX(&DACD2, 0, AUDIO_DAC_OFF_VALUE); 240 } 241 gptStopTimer(&AUDIO_STATE_TIMER); 242 } 243 244 void audio_driver_start_impl(void) { 245 if ((AUDIO_PIN == A4) || (AUDIO_PIN_ALT == A4)) { 246 dacStartConversion(&DACD1, &dac_conv_grp_ch1, (dacsample_t *)dac_buffer_1, AUDIO_DAC_BUFFER_SIZE); 247 } 248 if ((AUDIO_PIN == A5) || (AUDIO_PIN_ALT == A5)) { 249 dacStartConversion(&DACD2, &dac_conv_grp_ch2, (dacsample_t *)dac_buffer_2, AUDIO_DAC_BUFFER_SIZE); 250 } 251 gptStartContinuous(&AUDIO_STATE_TIMER, 2U); 252 } 253 254 #pragma GCC diagnostic pop