logo

qmk_firmware

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

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