logo

qmk_firmware

custom branch of QMK firmware git clone https://anongit.hacktivis.me/git/qmk_firmware.git

audio_pwm_software.c (5902B)


  1. /* Copyright 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. Audio Driver: PWM
  19. 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.
  20. this driver uses the chibios-PWM system to produce a square-wave on any given output pin in software
  21. - a pwm callback is used to set/clear the configured pin.
  22. */
  23. #include "audio.h"
  24. #include "gpio.h"
  25. #if !defined(AUDIO_PIN)
  26. # error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings"
  27. #endif
  28. extern bool playing_note;
  29. extern bool playing_melody;
  30. extern uint8_t note_timbre;
  31. static void pwm_audio_period_callback(PWMDriver *pwmp);
  32. static void pwm_audio_channel_interrupt_callback(PWMDriver *pwmp);
  33. static PWMConfig pwmCFG = {
  34. .frequency = 100000, /* PWM clock frequency */
  35. // 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
  36. .period = 2, /* initial PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */
  37. .callback = pwm_audio_period_callback,
  38. .channels =
  39. {
  40. // software-PWM just needs another callback on any channel
  41. {PWM_OUTPUT_ACTIVE_HIGH, pwm_audio_channel_interrupt_callback}, /* channel 0 -> TIMx_CH1 */
  42. {PWM_OUTPUT_DISABLED, NULL}, /* channel 1 -> TIMx_CH2 */
  43. {PWM_OUTPUT_DISABLED, NULL}, /* channel 2 -> TIMx_CH3 */
  44. {PWM_OUTPUT_DISABLED, NULL} /* channel 3 -> TIMx_CH4 */
  45. },
  46. };
  47. static float channel_1_frequency = 0.0f;
  48. void channel_1_set_frequency(float freq) {
  49. channel_1_frequency = freq;
  50. if (freq <= 0.0) // a pause/rest has freq=0
  51. return;
  52. pwmcnt_t period = (pwmCFG.frequency / freq);
  53. pwmChangePeriod(&AUDIO_PWM_DRIVER, period);
  54. pwmEnableChannel(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1,
  55. // adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH
  56. PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100));
  57. }
  58. float channel_1_get_frequency(void) {
  59. return channel_1_frequency;
  60. }
  61. void channel_1_start(void) {
  62. pwmStop(&AUDIO_PWM_DRIVER);
  63. pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
  64. pwmEnablePeriodicNotification(&AUDIO_PWM_DRIVER);
  65. pwmEnableChannelNotification(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1);
  66. }
  67. void channel_1_stop(void) {
  68. pwmStop(&AUDIO_PWM_DRIVER);
  69. palClearLine(AUDIO_PIN); // leave the line low, after last note was played
  70. #if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE)
  71. palClearLine(AUDIO_PIN_ALT); // leave the line low, after last note was played
  72. #endif
  73. }
  74. // generate a PWM signal on any pin, not necessarily the one connected to the timer
  75. static void pwm_audio_period_callback(PWMDriver *pwmp) {
  76. (void)pwmp;
  77. palClearLine(AUDIO_PIN);
  78. #if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE)
  79. palSetLine(AUDIO_PIN_ALT);
  80. #endif
  81. }
  82. static void pwm_audio_channel_interrupt_callback(PWMDriver *pwmp) {
  83. (void)pwmp;
  84. if (channel_1_frequency > 0) {
  85. palSetLine(AUDIO_PIN); // generate a PWM signal on any pin, not necessarily the one connected to the timer
  86. #if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE)
  87. palClearLine(AUDIO_PIN_ALT);
  88. #endif
  89. }
  90. }
  91. static void gpt_callback(GPTDriver *gptp);
  92. GPTConfig gptCFG = {
  93. /* a whole note is one beat, which is - per definition in musical_notes.h - set to 64
  94. the longest note is BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4
  95. the tempo (which might vary!) is in bpm (beats per minute)
  96. therefore: if the timer ticks away at .frequency = (60*64)Hz,
  97. and the .interval counts from 64 downwards - audio_update_state is
  98. called just often enough to not miss anything
  99. */
  100. .frequency = 60 * 64,
  101. .callback = gpt_callback,
  102. };
  103. void audio_driver_initialize_impl(void) {
  104. pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
  105. palSetLineMode(AUDIO_PIN, PAL_MODE_OUTPUT_PUSHPULL);
  106. palClearLine(AUDIO_PIN);
  107. #if defined(AUDIO_PIN_ALT) && defined(AUDIO_PIN_ALT_AS_NEGATIVE)
  108. palSetLineMode(AUDIO_PIN_ALT, PAL_MODE_OUTPUT_PUSHPULL);
  109. palClearLine(AUDIO_PIN_ALT);
  110. #endif
  111. pwmEnablePeriodicNotification(&AUDIO_PWM_DRIVER); // enable pwm callbacks
  112. pwmEnableChannelNotification(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1);
  113. gptStart(&AUDIO_STATE_TIMER, &gptCFG);
  114. }
  115. void audio_driver_start_impl(void) {
  116. channel_1_stop();
  117. channel_1_start();
  118. if (playing_note || playing_melody) {
  119. gptStartContinuous(&AUDIO_STATE_TIMER, 64);
  120. }
  121. }
  122. void audio_driver_stop_impl(void) {
  123. channel_1_stop();
  124. gptStopTimer(&AUDIO_STATE_TIMER);
  125. }
  126. /* a regular timer task, that checks the note to be currently played
  127. * and updates the pwm to output that frequency
  128. */
  129. static void gpt_callback(GPTDriver *gptp) {
  130. float freq; // TODO: freq_alt
  131. if (audio_update_state()) {
  132. freq = audio_get_processed_frequency(0); // freq_alt would be index=1
  133. channel_1_set_frequency(freq);
  134. }
  135. }