From 22215a0e920a237faabdfa1b8826cd110ab20c3b Mon Sep 17 00:00:00 2001 From: "Michael L. Walker" Date: Thu, 12 Apr 2018 08:16:35 -0700 Subject: [PATCH] Added support for audio using pins C4, C5, B6, B7 --- docs/config_options.md | 10 ++- docs/feature_audio.md | 13 ++- quantum/audio/audio.c | 192 +++++++++++++++++++++++++---------------- 3 files changed, 137 insertions(+), 78 deletions(-) diff --git a/docs/config_options.md b/docs/config_options.md index af8602c3f..d45592f82 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -61,10 +61,18 @@ This is a C header file that is one of the first things included, and will persi * COL2ROW or ROW2COL - how your matrix is configured. COL2ROW means the black mark on your diode is facing to the rows, and between the switch and the rows. * `#define AUDIO_VOICES` * turns on the alternate audio voices (to cycle through) +* `#define C4_AUDIO` + * enables audio on pin C4 +* `#define C5_AUDIO` + * enables audio on pin C5 * `#define C6_AUDIO` * enables audio on pin C6 * `#define B5_AUDIO` - * enables audio on pin B5 (duophony is enable if both are enabled) + * enables audio on pin B5 (duophony is enables if one of B[5-7]_AUDIO is enabled along with one of C[4-6]_AUDIO) +* `#define B6_AUDIO` + * enables audio on pin B6 (duophony is enables if one of B[5-7]_AUDIO is enabled along with one of C[4-6]_AUDIO) +* `#define B7_AUDIO` + * enables audio on pin B7 (duophony is enables if one of B[5-7]_AUDIO is enabled along with one of C[4-6]_AUDIO) * `#define BACKLIGHT_PIN B7` * pin of the backlight - B5, B6, B7 use PWM, others use softPWM * `#define BACKLIGHT_LEVELS 3` diff --git a/docs/feature_audio.md b/docs/feature_audio.md index 5b11aa3ab..eaaa2fe51 100644 --- a/docs/feature_audio.md +++ b/docs/feature_audio.md @@ -1,6 +1,17 @@ # Audio -Your keyboard can make sounds! If you've got a Planck, Preonic, or basically any AVR keyboard that allows access to the C6 or B5 port (`#define C6_AUDIO` and/or `#define B5_AUDIO`), you can hook up a simple speaker and make it beep. You can use those beeps to indicate layer transitions, modifiers, special keys, or just to play some funky 8bit tunes. +Your keyboard can make sounds! If you've got a Planck, Preonic, or basically any AVR keyboard that allows access to certain PWM-capable pins, you can hook up a simple speaker and make it beep. You can use those beeps to indicate layer transitions, modifiers, special keys, or just to play some funky 8bit tunes. + +Up to two simultaneous audio voices are supported, one driven by timer 1 and another driven by timer 3. The following pins can be defined as audio outputs in config.h: +Timer 1: +`#define B5_AUDIO` +`#define B6_AUDIO` +`#define B7_AUDIO` + +Timer 3: +`#define C4_AUDIO` +`#define C5_AUDIO` +`#define C6_AUDIO` If you add `AUDIO_ENABLE = yes` to your `rules.mk`, there's a couple different sounds that will automatically be enabled without any other configuration: diff --git a/quantum/audio/audio.c b/quantum/audio/audio.c index 85220e163..3c6d18c4f 100644 --- a/quantum/audio/audio.c +++ b/quantum/audio/audio.c @@ -35,44 +35,81 @@ // Timer Abstractions // ----------------------------------------------------------------------------- -// TIMSK3 - Timer/Counter #3 Interrupt Mask Register -// Turn on/off 3A interputs, stopping/enabling the ISR calls -#ifdef C6_AUDIO +//Currently we support timers 1 and 3 used at the sime time, channels A-C, +//pins PB5, PB6, PB7, PC4, PC5, and PC6 +#if defined(C6_AUDIO) + #define CPIN_AUDIO + #define CPIN_SET_DIRECTION DDRC |= _BV(PORTC6); + #define INIT_AUDIO_COUNTER_3 TCCR3A = (0 << COM3A1) | (0 << COM3A0) | (1 << WGM31) | (0 << WGM30); #define ENABLE_AUDIO_COUNTER_3_ISR TIMSK3 |= _BV(OCIE3A) #define DISABLE_AUDIO_COUNTER_3_ISR TIMSK3 &= ~_BV(OCIE3A) -#endif - -#ifdef B5_AUDIO - #define ENABLE_AUDIO_COUNTER_1_ISR TIMSK1 |= _BV(OCIE1A) - #define DISABLE_AUDIO_COUNTER_1_ISR TIMSK1 &= ~_BV(OCIE1A) -#endif - -// TCCR3A: Timer/Counter #3 Control Register -// Compare Output Mode (COM3An) = 0b00 = Normal port operation, OC3A disconnected from PC6 - -#ifdef C6_AUDIO #define ENABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A |= _BV(COM3A1); #define DISABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A &= ~(_BV(COM3A1) | _BV(COM3A0)); -#endif - -#ifdef B5_AUDIO - #define ENABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A |= _BV(COM1A1); - #define DISABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A &= ~(_BV(COM1A1) | _BV(COM1A0)); -#endif - -// Fast PWM Mode Controls - -#ifdef C6_AUDIO #define TIMER_3_PERIOD ICR3 #define TIMER_3_DUTY_CYCLE OCR3A + #define TIMER3_AUDIO_vect TIMER3_COMPA_vect +#endif +#if defined(C5_AUDIO) + #define CPIN_AUDIO + #define CPIN_SET_DIRECTION DDRC |= _BV(PORTC5); + #define INIT_AUDIO_COUNTER_3 TCCR3A = (0 << COM3B1) | (0 << COM3B0) | (1 << WGM31) | (0 << WGM30); + #define ENABLE_AUDIO_COUNTER_3_ISR TIMSK3 |= _BV(OCIE3B) + #define DISABLE_AUDIO_COUNTER_3_ISR TIMSK3 &= ~_BV(OCIE3B) + #define ENABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A |= _BV(COM3B1); + #define DISABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A &= ~(_BV(COM3B1) | _BV(COM3B0)); + #define TIMER_3_PERIOD ICR3 + #define TIMER_3_DUTY_CYCLE OCR3B + #define TIMER3_AUDIO_vect TIMER3_COMPB_vect +#endif +#if defined(C4_AUDIO) + #define CPIN_AUDIO + #define CPIN_SET_DIRECTION DDRC |= _BV(PORTC4); + #define INIT_AUDIO_COUNTER_3 TCCR3A = (0 << COM3C1) | (0 << COM3C0) | (1 << WGM31) | (0 << WGM30); + #define ENABLE_AUDIO_COUNTER_3_ISR TIMSK3 |= _BV(OCIE3C) + #define DISABLE_AUDIO_COUNTER_3_ISR TIMSK3 &= ~_BV(OCIE3C) + #define ENABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A |= _BV(COM3C1); + #define DISABLE_AUDIO_COUNTER_3_OUTPUT TCCR3A &= ~(_BV(COM3C1) | _BV(COM3C0)); + #define TIMER_3_PERIOD ICR3 + #define TIMER_3_DUTY_CYCLE OCR3C + #define TIMER3_AUDIO_vect TIMER3_COMPC_vect #endif -#ifdef B5_AUDIO +#if defined(B5_AUDIO) + #define BPIN_AUDIO + #define BPIN_SET_DIRECTION DDRC |= _BV(PORTB5); + #define INIT_AUDIO_COUNTER_1 TCCR1A = (0 << COM1A1) | (0 << COM1A0) | (1 << WGM11) | (0 << WGM10); + #define ENABLE_AUDIO_COUNTER_1_ISR TIMSK1 |= _BV(OCIE1A) + #define DISABLE_AUDIO_COUNTER_1_ISR TIMSK1 &= ~_BV(OCIE1A) + #define ENABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A |= _BV(COM1A1); + #define DISABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A &= ~(_BV(COM1A1) | _BV(COM1A0)); #define TIMER_1_PERIOD ICR1 #define TIMER_1_DUTY_CYCLE OCR1A + #define TIMER1_AUDIO_vect TIMER1_COMPA_vect +#endif +#if defined(B6_AUDIO) + #define BPIN_AUDIO + #define BPIN_SET_DIRECTION DDRC |= _BV(PORTB6); + #define INIT_AUDIO_COUNTER_1 TCCR1A = (0 << COM1B1) | (0 << COM1B0) | (1 << WGM11) | (0 << WGM10); + #define ENABLE_AUDIO_COUNTER_1_ISR TIMSK1 |= _BV(OCIE1B) + #define DISABLE_AUDIO_COUNTER_1_ISR TIMSK1 &= ~_BV(OCIE1B) + #define ENABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A |= _BV(COM1B1); + #define DISABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A &= ~(_BV(COM1B1) | _BV(COM1B0)); + #define TIMER_1_PERIOD ICR1 + #define TIMER_1_DUTY_CYCLE OCR1B + #define TIMER1_AUDIO_vect TIMER1_COMPB_vect +#endif +#if defined(B7_AUDIO) + #define BPIN_AUDIO + #define BPIN_SET_DIRECTION DDRC |= _BV(PORTB7); + #define INIT_AUDIO_COUNTER_1 TCCR1A = (0 << COM1C1) | (0 << COM1C0) | (1 << WGM11) | (0 << WGM10); + #define ENABLE_AUDIO_COUNTER_1_ISR TIMSK1 |= _BV(OCIE1C) + #define DISABLE_AUDIO_COUNTER_1_ISR TIMSK1 &= ~_BV(OCIE1C) + #define ENABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A |= _BV(COM1C1); + #define DISABLE_AUDIO_COUNTER_1_OUTPUT TCCR1A &= ~(_BV(COM1C1) | _BV(COM1C0)); + #define TIMER_1_PERIOD ICR1 + #define TIMER_1_DUTY_CYCLE OCR1C + #define TIMER1_AUDIO_vect TIMER1_COMPC_vect #endif - - // ----------------------------------------------------------------------------- @@ -147,47 +184,51 @@ void audio_init() if (!audio_initialized) { - // Set port PC6 (OC3A and /OC4A) as output - - #ifdef C6_AUDIO - DDRC |= _BV(PORTC6); - //#else - // DDRC |= _BV(PORTC6); // Why is PC6 being set as output low, if C6_audio isn't defined? - // PORTC &= ~_BV(PORTC6); + // Set audio ports as output + #ifdef CPIN_AUDIO + CPIN_SET_DIRECTION + #endif + #ifdef BPIN_AUDIO + BPIN_SET_DIRECTION #endif - #ifdef B5_AUDIO - DDRB |= _BV(PORTB5); - //#else - // DDRB |= _BV(PORTB5); // Same as with PC6 - // PORTB &= ~_BV(PORTB5); - #endif - - #ifdef C6_AUDIO + #ifdef CPIN_AUDIO DISABLE_AUDIO_COUNTER_3_ISR; #endif - - #ifdef B5_AUDIO + #ifdef BPIN_AUDIO DISABLE_AUDIO_COUNTER_1_ISR; #endif - // TCCR3A / TCCR3B: Timer/Counter #3 Control Registers - // Compare Output Mode (COM3An) = 0b00 = Normal port operation, OC3A disconnected from PC6 - // Waveform Generation Mode (WGM3n) = 0b1110 = Fast PWM Mode 14 (Period = ICR3, Duty Cycle = OCR3A) + // TCCR3A / TCCR3B: Timer/Counter #3 Control Registers TCCR3A/TCCR3B, TCCR1A/TCCR1B + // Compare Output Mode (COM3An and COM1An) = 0b00 = Normal port operation + // OC3A -- PC6 + // OC3B -- PC5 + // OC3C -- PC4 + // OC1A -- PB5 + // OC1B -- PB6 + // OC1C -- PB7 + + // Waveform Generation Mode (WGM3n) = 0b1110 = Fast PWM Mode 14. Period = ICR3, Duty Cycle OCR3A) + // OCR3A - PC6 + // OCR3B - PC5 + // OCR3C - PC4 + // OCR1A - PB5 + // OCR1B - PB6 + // OCR1C - PB7 + // Clock Select (CS3n) = 0b010 = Clock / 8 - - #ifdef C6_AUDIO - TCCR3A = (0 << COM3A1) | (0 << COM3A0) | (1 << WGM31) | (0 << WGM30); + #ifdef CPIN_AUDIO + INIT_AUDIO_COUNTER_3 TCCR3B = (1 << WGM33) | (1 << WGM32) | (0 << CS32) | (1 << CS31) | (0 << CS30); + TIMER_3_PERIOD = (uint16_t)(((float)F_CPU) / (440 * CPU_PRESCALER)); + TIMER_3_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (440 * CPU_PRESCALER)) * note_timbre); #endif - - #ifdef B5_AUDIO - TCCR1A = (0 << COM1A1) | (0 << COM1A0) | (1 << WGM11) | (0 << WGM10); + #ifdef BPIN_AUDIO + INIT_AUDIO_COUNTER_1 TCCR1B = (1 << WGM13) | (1 << WGM12) | (0 << CS12) | (1 << CS11) | (0 << CS10); - TIMER_1_PERIOD = (uint16_t)(((float)F_CPU) / (440 * CPU_PRESCALER)); TIMER_1_DUTY_CYCLE = (uint16_t)((((float)F_CPU) / (440 * CPU_PRESCALER)) * note_timbre); - #endif + #endif audio_initialized = true; } @@ -207,13 +248,12 @@ void stop_all_notes() } voices = 0; - - #ifdef C6_AUDIO + #ifdef CPIN_AUDIO DISABLE_AUDIO_COUNTER_3_ISR; DISABLE_AUDIO_COUNTER_3_OUTPUT; #endif - #ifdef B5_AUDIO + #ifdef BPIN_AUDIO DISABLE_AUDIO_COUNTER_1_ISR; DISABLE_AUDIO_COUNTER_1_OUTPUT; #endif @@ -259,11 +299,11 @@ void stop_note(float freq) voice_place = 0; } if (voices == 0) { - #ifdef C6_AUDIO + #ifdef CPIN_AUDIO DISABLE_AUDIO_COUNTER_3_ISR; DISABLE_AUDIO_COUNTER_3_OUTPUT; #endif - #ifdef B5_AUDIO + #ifdef BPIN_AUDIO DISABLE_AUDIO_COUNTER_1_ISR; DISABLE_AUDIO_COUNTER_1_OUTPUT; #endif @@ -295,15 +335,15 @@ float vibrato(float average_freq) { #endif -#ifdef C6_AUDIO -ISR(TIMER3_COMPA_vect) +#ifdef CPIN_AUDIO +ISR(TIMER3_AUDIO_vect) { float freq; if (playing_note) { if (voices > 0) { - #ifdef B5_AUDIO + #ifdef BPIN_AUDIO float freq_alt = 0; if (voices > 1) { if (polyphony_rate == 0) { @@ -477,10 +517,10 @@ ISR(TIMER3_COMPA_vect) } #endif -#ifdef B5_AUDIO -ISR(TIMER1_COMPA_vect) +#ifdef BPIN_AUDIO +ISR(TIMER1_AUDIO_vect) { - #if defined(B5_AUDIO) && !defined(C6_AUDIO) + #if defined(BPIN_AUDIO) && !defined(CPIN_AUDIO) float freq = 0; if (playing_note) { @@ -627,10 +667,10 @@ void play_note(float freq, int vol) { } if (audio_config.enable && voices < 8) { - #ifdef C6_AUDIO + #ifdef CPIN_AUDIO DISABLE_AUDIO_COUNTER_3_ISR; #endif - #ifdef B5_AUDIO + #ifdef BPIN_AUDIO DISABLE_AUDIO_COUNTER_1_ISR; #endif @@ -648,12 +688,12 @@ void play_note(float freq, int vol) { voices++; } - #ifdef C6_AUDIO + #ifdef CPIN_AUDIO ENABLE_AUDIO_COUNTER_3_ISR; ENABLE_AUDIO_COUNTER_3_OUTPUT; #endif - #ifdef B5_AUDIO - #ifdef C6_AUDIO + #ifdef BPIN_AUDIO + #ifdef CPIN_AUDIO if (voices > 1) { ENABLE_AUDIO_COUNTER_1_ISR; ENABLE_AUDIO_COUNTER_1_OUTPUT; @@ -676,10 +716,10 @@ void play_notes(float (*np)[][2], uint16_t n_count, bool n_repeat) if (audio_config.enable) { - #ifdef C6_AUDIO + #ifdef CPIN_AUDIO DISABLE_AUDIO_COUNTER_3_ISR; #endif - #ifdef B5_AUDIO + #ifdef BPIN_AUDIO DISABLE_AUDIO_COUNTER_1_ISR; #endif @@ -701,12 +741,12 @@ void play_notes(float (*np)[][2], uint16_t n_count, bool n_repeat) note_position = 0; - #ifdef C6_AUDIO + #ifdef CPIN_AUDIO ENABLE_AUDIO_COUNTER_3_ISR; ENABLE_AUDIO_COUNTER_3_OUTPUT; #endif - #ifdef B5_AUDIO - #ifndef C6_AUDIO + #ifdef BPIN_AUDIO + #ifndef CPIN_AUDIO ENABLE_AUDIO_COUNTER_1_ISR; ENABLE_AUDIO_COUNTER_1_OUTPUT; #endif