summaryrefslogtreecommitdiff
path: root/quantum/audio/driver_chibios_pwm_hardware.c
diff options
context:
space:
mode:
Diffstat (limited to 'quantum/audio/driver_chibios_pwm_hardware.c')
-rw-r--r--quantum/audio/driver_chibios_pwm_hardware.c144
1 files changed, 144 insertions, 0 deletions
diff --git a/quantum/audio/driver_chibios_pwm_hardware.c b/quantum/audio/driver_chibios_pwm_hardware.c
new file mode 100644
index 0000000000..3c7d89b290
--- /dev/null
+++ b/quantum/audio/driver_chibios_pwm_hardware.c
@@ -0,0 +1,144 @@
+/* Copyright 2020 Jack Humbert
+ * Copyright 2020 JohSchneider
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+Audio Driver: PWM
+
+the duty-cycle is always kept at 50%, and the pwm-period is adjusted to match the frequency of a note to be played back.
+
+this driver uses the chibios-PWM system to produce a square-wave on specific output pins that are connected to the PWM hardware.
+The hardware directly toggles the pin via its alternate function. see your MCUs data-sheet for which pin can be driven by what timer - looking for TIMx_CHy and the corresponding alternate function.
+
+ */
+
+#include "audio.h"
+#include "ch.h"
+#include "hal.h"
+
+#if !defined(AUDIO_PIN)
+# error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings"
+#endif
+
+extern bool playing_note;
+extern bool playing_melody;
+extern uint8_t note_timbre;
+
+static PWMConfig pwmCFG = {
+ .frequency = 100000, /* PWM clock frequency */
+ // CHIBIOS-BUG? can't set the initial period to <2, or the pwm (hard or software) takes ~130ms with .frequency=500000 for a pwmChangePeriod to take effect; with no output=silence in the meantime
+ .period = 2, /* initial PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */
+ .callback = NULL, /* no callback, the hardware directly toggles the pin */
+ .channels =
+ {
+#if AUDIO_PWM_CHANNEL == 4
+ {PWM_OUTPUT_DISABLED, NULL}, /* channel 0 -> TIMx_CH1 */
+ {PWM_OUTPUT_DISABLED, NULL}, /* channel 1 -> TIMx_CH2 */
+ {PWM_OUTPUT_DISABLED, NULL}, /* channel 2 -> TIMx_CH3 */
+ {PWM_OUTPUT_ACTIVE_HIGH, NULL} /* channel 3 -> TIMx_CH4 */
+#elif AUDIO_PWM_CHANNEL == 3
+ {PWM_OUTPUT_DISABLED, NULL},
+ {PWM_OUTPUT_DISABLED, NULL},
+ {PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH3 */
+ {PWM_OUTPUT_DISABLED, NULL}
+#elif AUDIO_PWM_CHANNEL == 2
+ {PWM_OUTPUT_DISABLED, NULL},
+ {PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH2 */
+ {PWM_OUTPUT_DISABLED, NULL},
+ {PWM_OUTPUT_DISABLED, NULL}
+#else /*fallback to CH1 */
+ {PWM_OUTPUT_ACTIVE_HIGH, NULL}, /* TIMx_CH1 */
+ {PWM_OUTPUT_DISABLED, NULL},
+ {PWM_OUTPUT_DISABLED, NULL},
+ {PWM_OUTPUT_DISABLED, NULL}
+#endif
+ },
+};
+
+static float channel_1_frequency = 0.0f;
+void channel_1_set_frequency(float freq) {
+ channel_1_frequency = freq;
+
+ if (freq <= 0.0) // a pause/rest has freq=0
+ return;
+
+ pwmcnt_t period = (pwmCFG.frequency / freq);
+ pwmChangePeriod(&AUDIO_PWM_DRIVER, period);
+ pwmEnableChannel(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1,
+ // adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH
+ PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100));
+}
+
+float channel_1_get_frequency(void) { return channel_1_frequency; }
+
+void channel_1_start(void) {
+ pwmStop(&AUDIO_PWM_DRIVER);
+ pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
+}
+
+void channel_1_stop(void) { pwmStop(&AUDIO_PWM_DRIVER); }
+
+static void gpt_callback(GPTDriver *gptp);
+GPTConfig gptCFG = {
+ /* a whole note is one beat, which is - per definition in musical_notes.h - set to 64
+ the longest note is BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4
+ the tempo (which might vary!) is in bpm (beats per minute)
+ therefore: if the timer ticks away at .frequency = (60*64)Hz,
+ and the .interval counts from 64 downwards - audio_update_state is
+ called just often enough to not miss any notes
+ */
+ .frequency = 60 * 64,
+ .callback = gpt_callback,
+};
+
+void audio_driver_initialize(void) {
+ pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
+
+ // connect the AUDIO_PIN to the PWM hardware
+#if defined(USE_GPIOV1) // STM32F103C8
+ palSetLineMode(AUDIO_PIN, PAL_MODE_STM32_ALTERNATE_PUSHPULL);
+#else // GPIOv2 (or GPIOv3 for f4xx, which is the same/compatible at this command)
+ palSetLineMode(AUDIO_PIN, PAL_STM32_MODE_ALTERNATE | PAL_STM32_ALTERNATE(AUDIO_PWM_PAL_MODE));
+#endif
+
+ gptStart(&AUDIO_STATE_TIMER, &gptCFG);
+}
+
+void audio_driver_start(void) {
+ channel_1_stop();
+ channel_1_start();
+
+ if (playing_note || playing_melody) {
+ gptStartContinuous(&AUDIO_STATE_TIMER, 64);
+ }
+}
+
+void audio_driver_stop(void) {
+ channel_1_stop();
+ gptStopTimer(&AUDIO_STATE_TIMER);
+}
+
+/* a regular timer task, that checks the note to be currently played
+ * and updates the pwm to output that frequency
+ */
+static void gpt_callback(GPTDriver *gptp) {
+ float freq; // TODO: freq_alt
+
+ if (audio_update_state()) {
+ freq = audio_get_processed_frequency(0); // freq_alt would be index=1
+ channel_1_set_frequency(freq);
+ }
+}