audio_pwm_hardware.c 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  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 "ch.h"
  14. #include "hal.h"
  15. #if !defined(AUDIO_PIN)
  16. # error "Audio feature enabled, but no pin selected - see docs/feature_audio under the ARM PWM settings"
  17. #endif
  18. #if !defined(AUDIO_PWM_COUNTER_FREQUENCY)
  19. # define AUDIO_PWM_COUNTER_FREQUENCY 100000
  20. #endif
  21. extern bool playing_note;
  22. extern bool playing_melody;
  23. extern uint8_t note_timbre;
  24. static PWMConfig pwmCFG = {.frequency = AUDIO_PWM_COUNTER_FREQUENCY, /* PWM clock frequency */
  25. .period = 2,
  26. .callback = NULL,
  27. .channels = {[(AUDIO_PWM_CHANNEL - 1)] = {.mode = PWM_OUTPUT_ACTIVE_HIGH, .callback = NULL}}};
  28. static float channel_1_frequency = 0.0f;
  29. void channel_1_set_frequency(float freq) {
  30. channel_1_frequency = freq;
  31. if (freq <= 0.0) {
  32. // a pause/rest has freq=0
  33. return;
  34. }
  35. pwmcnt_t period = (pwmCFG.frequency / freq);
  36. chSysLockFromISR();
  37. pwmChangePeriodI(&AUDIO_PWM_DRIVER, period);
  38. pwmEnableChannelI(&AUDIO_PWM_DRIVER, AUDIO_PWM_CHANNEL - 1,
  39. // adjust the duty-cycle so that the output is for 'note_timbre' duration HIGH
  40. PWM_PERCENTAGE_TO_WIDTH(&AUDIO_PWM_DRIVER, (100 - note_timbre) * 100));
  41. chSysUnlockFromISR();
  42. }
  43. float channel_1_get_frequency(void) {
  44. return channel_1_frequency;
  45. }
  46. void channel_1_start(void) {
  47. pwmStop(&AUDIO_PWM_DRIVER);
  48. pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
  49. }
  50. void channel_1_stop(void) {
  51. pwmStop(&AUDIO_PWM_DRIVER);
  52. }
  53. static virtual_timer_t audio_vt;
  54. static void audio_callback(virtual_timer_t *vtp, void *p);
  55. // a regular timer task, that checks the note to be currently played and updates
  56. // the pwm to output that frequency.
  57. static void audio_callback(virtual_timer_t *vtp, void *p) {
  58. float freq; // TODO: freq_alt
  59. if (audio_update_state()) {
  60. freq = audio_get_processed_frequency(0); // freq_alt would be index=1
  61. channel_1_set_frequency(freq);
  62. }
  63. chSysLockFromISR();
  64. chVTSetI(&audio_vt, TIME_MS2I(16), audio_callback, NULL);
  65. chSysUnlockFromISR();
  66. }
  67. void audio_driver_initialize(void) {
  68. pwmStart(&AUDIO_PWM_DRIVER, &pwmCFG);
  69. // connect the AUDIO_PIN to the PWM hardware
  70. #if defined(USE_GPIOV1) // STM32F103C8, RP2040
  71. palSetLineMode(AUDIO_PIN, AUDIO_PWM_PAL_MODE);
  72. #else // GPIOv2 (or GPIOv3 for f4xx, which is the same/compatible at this command)
  73. palSetLineMode(AUDIO_PIN, PAL_MODE_ALTERNATE(AUDIO_PWM_PAL_MODE));
  74. #endif
  75. chVTObjectInit(&audio_vt);
  76. }
  77. void audio_driver_start(void) {
  78. channel_1_stop();
  79. channel_1_start();
  80. if ((playing_note || playing_melody) && !chVTIsArmed(&audio_vt)) {
  81. // a whole note is one beat, which is - per definition in
  82. // musical_notes.h - set to 64 the longest note is
  83. // BREAVE_DOT=128+64=192, the shortest SIXTEENTH=4 the tempo (which
  84. // might vary!) is in bpm (beats per minute) therefore: if the timer
  85. // ticks away at 64Hz (~16.6ms) audio_update_state is called just often
  86. // enough to not miss any notes
  87. chVTSet(&audio_vt, TIME_MS2I(16), audio_callback, NULL);
  88. }
  89. }
  90. void audio_driver_stop(void) {
  91. channel_1_stop();
  92. chVTReset(&audio_vt);
  93. }