DIY-Arduino-variometer/libraries/toneAC/toneAC.cpp
2014-01-17 15:44:37 +01:00

68 lines
3.3 KiB
C++

// ---------------------------------------------------------------------------
// Created by Tim Eckel - teckel@leethost.com
// Copyright 2013 License: GNU GPL v3 http://www.gnu.org/licenses/gpl-3.0.html
//
// See "toneAC.h" for purpose, syntax, version history, links, and more.
// ---------------------------------------------------------------------------
#include "toneAC.h"
unsigned long _tAC_time; // Used to track end note with timer when playing note in the background.
#ifndef TONEAC_TINY
uint8_t _tAC_volume[] = { 200, 100, 67, 50, 40, 33, 29, 22, 11, 2 }; // Duty for linear volume control.
#endif
#ifndef TONEAC_TINY
void toneAC(unsigned long frequency, uint8_t volume, unsigned long length, uint8_t background) {
if (frequency == 0 || volume == 0) { noToneAC(); return; } // If frequency or volume are 0, turn off sound and return.
if (volume > 10) volume = 10; // Make sure volume is in range (1 to 10).
#else
void toneAC(unsigned long frequency, unsigned long length) {
if (frequency == 0) { noToneAC(); return; } // If frequency is 0, turn off sound and return.
#endif
PWMT1DREG |= _BV(PWMT1AMASK) | _BV(PWMT1BMASK); // Set timer 1 PWM pins to OUTPUT (because analogWrite does it too).
uint8_t prescaler = _BV(CS10); // Try using prescaler 1 first.
unsigned long top = F_CPU / frequency / 2 - 1; // Calculate the top.
if (top > 65535) { // If not in the range for prescaler 1, use prescaler 256 (122 Hz and lower @ 16 MHz).
prescaler = _BV(CS12); // Set the 256 prescaler bit.
top = top / 256 - 1; // Calculate the top using prescaler 256.
}
#ifndef TONEAC_TINY
unsigned int duty = top / _tAC_volume[volume - 1]; // Calculate the duty cycle (volume).
#else
unsigned int duty = top >> 1; // 50% duty cycle (loudest and highest quality).
#endif
#ifndef TONEAC_TINY
if (length > 0 && background) { // Background tone playing, returns control to your sketch.
#else
if (length > 0) { // Background tone playing, returns control to your sketch.
#endif
_tAC_time = millis() + length; // Set when the note should end.
TIMSK1 |= _BV(OCIE1A); // Activate the timer interrupt.
}
ICR1 = top; // Set the top.
if (TCNT1 > top) TCNT1 = top; // Counter over the top, put within range.
TCCR1B = _BV(WGM13) | prescaler; // Set PWM, phase and frequency corrected (top=ICR1) and prescaler.
OCR1A = OCR1B = duty; // Set the duty cycle (volume).
TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(COM1B0); // Inverted/non-inverted mode (AC).
#ifndef TONEAC_TINY
if (length > 0 && !background) { delay(length); noToneAC(); } // Just a simple delay, doesn't return control till finished.
#endif
}
void noToneAC() {
TIMSK1 &= ~_BV(OCIE1A); // Remove the timer interrupt.
TCCR1B = _BV(CS11); // Default clock prescaler of 8.
TCCR1A = _BV(WGM10); // Set to defaults so PWM can work like normal (PWM, phase corrected, 8bit).
PWMT1PORT &= ~_BV(PWMT1AMASK); // Set timer 1 PWM pins to LOW.
PWMT1PORT &= ~_BV(PWMT1BMASK); // Other timer 1 PWM pin also to LOW.
}
ISR(TIMER1_COMPA_vect) { // Timer interrupt vector.
if (millis() >= _tAC_time) noToneAC(); // Check to see if it's time for the note to end.
}