|
| 1 | + |
| 2 | +#pragma once |
| 3 | +#include "AudioConfig.h" |
| 4 | +#ifdef __AVR__ |
| 5 | +#include "AudioPWM/PWMAudioStreamBase.h" |
| 6 | + |
| 7 | +namespace audio_tools { |
| 8 | + |
| 9 | +class PWMAudioStreamAVR; |
| 10 | +typedef PWMAudioStreamAVR PWMAudioStream; |
| 11 | +static PWMAudioStreamAVR *accessAudioPWM = nullptr; |
| 12 | + |
| 13 | + |
| 14 | +/** |
| 15 | + * @brief Experimental: Audio output to PWM pins for the AVR. The AVR supports only up to 2 channels. |
| 16 | + * @author Phil Schatzmann |
| 17 | + * @copyright GPLv3 |
| 18 | + */ |
| 19 | + |
| 20 | +class PWMAudioStreamAVR : public PWMAudioStreamBase { |
| 21 | + friend void defaultPWMAudioOutputCallback(); |
| 22 | + |
| 23 | + public: |
| 24 | + |
| 25 | + PWMAudioStreamAVR(){ |
| 26 | + LOGD("PWMAudioStreamAVR"); |
| 27 | + accessAudioPWM = this; |
| 28 | + } |
| 29 | + |
| 30 | + virtual int maxChannels() { |
| 31 | + return 2; |
| 32 | + }; |
| 33 | + |
| 34 | + // Ends the output |
| 35 | + virtual void end(){ |
| 36 | + LOGD(__FUNCTION__); |
| 37 | + noInterrupts(); |
| 38 | + // stop timer callback |
| 39 | + TCCR1B = 0; |
| 40 | + // stop pwm timers |
| 41 | + TCCR2A = 0; |
| 42 | + interrupts(); // enable all interrupts |
| 43 | + |
| 44 | + data_write_started = false; |
| 45 | + } |
| 46 | + |
| 47 | + /// Setup AVR timer with callback |
| 48 | + void setupTimer() { |
| 49 | + LOGD(__FUNCTION__); |
| 50 | + // CPU Frequency 16 MHz |
| 51 | + // prescaler 1, 256 or 1024 => no prescaling |
| 52 | + uint32_t steps = F_CPU / 8 / audio_config.sample_rate; // e.g. (16000000/8/44100=>45) |
| 53 | + if (steps>65535){ |
| 54 | + LOGE("requested sample rate not supported: %d - we use %d",audio_config.sample_rate, F_CPU / 65536); |
| 55 | + steps = 65535; |
| 56 | + } else { |
| 57 | + LOGD("compare match register set to %d",steps); |
| 58 | + } |
| 59 | + |
| 60 | + // setup timer intterupt |
| 61 | + noInterrupts(); |
| 62 | + TCCR1B = 0; |
| 63 | + //compare match register |
| 64 | + OCR1A = steps; |
| 65 | + TCCR1B |= (1 << WGM12); // CTC mode |
| 66 | + //TCCR1B |= (1 << CS10); // prescaler 1 |
| 67 | + TCCR1B |= (1 << CS11); // prescaler 8 |
| 68 | + TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt |
| 69 | + interrupts(); // enable all interrupts |
| 70 | + } |
| 71 | + |
| 72 | + /// Setup LED PWM |
| 73 | + void setupPWM(){ |
| 74 | + LOGD(__FUNCTION__); |
| 75 | + if (audio_config.channels>2) { |
| 76 | + LOGW("Max 2 channels supported - you requested %d", audio_config.channels); |
| 77 | + audio_config.channels = 2; |
| 78 | + } |
| 79 | + |
| 80 | + for (int j=0;j<audio_config.channels;j++){ |
| 81 | + LOGD("Processing channel %d", j); |
| 82 | + setupPin(pins[j]); |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + |
| 87 | + // Timer 0 is used by Arduino! |
| 88 | + // Timer 1 is used to drive output in sample_rate |
| 89 | + // => only Timer2 is available for PWM |
| 90 | + void setupPin(int pin){ |
| 91 | + switch(pin){ |
| 92 | + case 3:case 11: |
| 93 | + // switch PWM frequency to 62500.00 Hz |
| 94 | + TCCR2B = TCCR2B & B11111000 | B00000001; |
| 95 | + LOGI("PWM Frequency changed for D3 and D11"); |
| 96 | + break; |
| 97 | + |
| 98 | + default: |
| 99 | + LOGE("PWM Unsupported pin: %d", pin); |
| 100 | + break; |
| 101 | + |
| 102 | + } |
| 103 | + pinMode(pin, OUTPUT); |
| 104 | + } |
| 105 | + |
| 106 | + virtual void pwmWrite(int channel, int value){ |
| 107 | + analogWrite(pins[channel], value); |
| 108 | + } |
| 109 | + |
| 110 | + void logConfig(){ |
| 111 | + audio_config.logConfig(); |
| 112 | + LOGI("pwm freq: %f khz", 62.5); |
| 113 | + if (audio_config.channels==1) { |
| 114 | + LOGI("output pin: %d", pins[0]); |
| 115 | + } else { |
| 116 | + LOGI("output pins: %d / %d", pins[0],pins[1]); |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + protected: |
| 121 | + int pins[2] = {3, 11}; |
| 122 | + |
| 123 | +}; |
| 124 | + |
| 125 | +/// separate method that can be defined as friend so that we can access protected information |
| 126 | +void defaultPWMAudioOutputCallback(){ |
| 127 | + if (accessAudioPWM!=nullptr && accessAudioPWM->data_write_started){ |
| 128 | + accessAudioPWM->playNextFrame(); |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +/// timer callback: write the next frame to the pins |
| 133 | +ISR(TIMER1_COMPA_vect){ |
| 134 | + defaultPWMAudioOutputCallback(); |
| 135 | +} |
| 136 | + |
| 137 | +} // Namespace |
| 138 | + |
| 139 | + |
| 140 | +#endif |
| 141 | + |
0 commit comments