qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

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 }