68 lines
3.3 KiB
C++
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.
|
|
} |