Go Down

Topic: TimerFreeTone Library v1.5: Play tones without timers and therefore no conflicts (Read 17462 times) previous topic - next topic

teckel

I've already written 3 alternative tone libraries (toneAC, toneAC2 & NewTone).  However, a user asked for a way to create tones without using any timers as he had conflicts with both timer 1 and timer 2.  This seemed like a perfect opportunity to confuse people further by creating a 4th library that did essentially the same thing, generate sound.  So, I wrote up sample code that evolved into the TimerFreeTone library.  As a bonus, it works with all ATmega/ATtiny AVR microcontrollers as well as the Arduino Due, Teensy 3.x and other ARM-based microcontrollers equally well.  As a second bonus, it allows you to set a volume level per note.

Download TimerFreeTone v1.5

v1.5 - Fixed problem with latest release of the Arduino IDE which caused the library to totally stop functioning. Adjusted note duration to not fail on timer rollover. Now delays for note duration when frequency or volume are zero.

v1.4 Added optional volume parameter.

v1.3 Fixed problem with long tone play durations. Changed the way the note duration is calculated from a suggestion by Paul Stoffregen.

v1.2 calculates duration differently for higher tone accuracy and smaller code size.

v1.1 added a few small changes (after I got around to actually tested the library first-hand with an Arduino).

Code: [Select]
#include <TimerFreeTone.h>

#define TONE_PIN 10 // Pin you have speaker/piezo connected to (be sure to include a 100 ohm resistor).

int melody[] = { 262, 196, 196, 220, 196, 0, 247, 262 };
int duration[] = { 250, 125, 125, 250, 250, 250, 250, 250 };

void setup() {
  for (int thisNote = 0; thisNote < 8; thisNote++) { // Loop through the notes in the array.
    TimerFreeTone(TONE_PIN, melody[thisNote], duration[thisNote]); // Play thisNote for duration.
    delay(50); // Short delay between notes.
  }
}

void loop() {}


TimerFreeTone Library home page

Tim
My platforms Arduino, Teensy 3.2, Arduino Pro Mini, ATmega328
My libraries: NewPing, LCDBitmap, toneAC, toneAC2, NewTone, TimerFreeTone
My projects: https://dogblocker.com & https://baconorbeer.com
My beer: Great Lakes Brewing Co. Lake Erie Monster

robtillaart

imho more choices is a good thing, especially if this new lib is on the same quality level of previous work!
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

teckel

New version of the Timer FreeTone library released.

Download TimerFreeTone v1.1

This release was actually tested on an Arduino, so I'm sure it works now.  I also fixed/changed a few things.  It generates code that's a bit smaller as well.  Let me know how it works for you!

Tim
My platforms Arduino, Teensy 3.2, Arduino Pro Mini, ATmega328
My libraries: NewPing, LCDBitmap, toneAC, toneAC2, NewTone, TimerFreeTone
My projects: https://dogblocker.com & https://baconorbeer.com
My beer: Great Lakes Brewing Co. Lake Erie Monster

evancleary

hi Tim,

thanks for the library, I am trying to play a tone before a for loop but when i place it there in the code the tone repeats and the code freezes

any ideas?

Code: [Select]
#include <TimerFreeTone.h>
#include <SevenSeg.h>


SevenSeg disp1(30,31,32,33,34,35,36);
SevenSeg disp2(40,41,42,43,44,45,46);

const int numOfDigits1 = 3;
const int numOfDigits2 = 2;
int digitPins1[numOfDigits1] = {2,3,4};
int digitPins2[numOfDigits2] = {5,6};

void setup() {
  disp1.setDigitPins(numOfDigits1,digitPins1);
  disp1.setDigitDelay(2000);
  disp1.setTimer(1);
  disp1.startTimer();
 
  disp2.setDigitPins(numOfDigits2,digitPins2);
  disp2.setDigitDelay(2000);
  disp2.setTimer(2);
  disp2.startTimer();
 
  disp1.setCommonCathode();
  disp2.setCommonCathode();
 

}

void loop() {
  disp2.write(23);
  TimerFreeTone(13, 500, 500);
 for (int i = 180; i >= 0; i--){
 
            disp1.writeClock(i);
            delay(100);
 }
 
}

ISR(TIMER1_COMPA_vect){
  disp1.interruptAction();
}

ISR(TIMER2_COMPA_vect){
  disp2.interruptAction();
}

teckel

New version of the Timer FreeTone library released.

Download TimerFreeTone v1.1

This release was actually tested on an Arduino, so I'm sure it works now.  I also fixed/changed a few things.  It generates code that's a bit smaller as well.  Let me know how it works for you!

Tim
hi Tim,

thanks for the library, I am trying to play a tone before a for loop but when i place it there in the code the tone repeats and the code freezes

any ideas?

Code: [Select]
#include <TimerFreeTone.h>
#include <SevenSeg.h>


SevenSeg disp1(30,31,32,33,34,35,36);
SevenSeg disp2(40,41,42,43,44,45,46);

const int numOfDigits1 = 3;
const int numOfDigits2 = 2;
int digitPins1[numOfDigits1] = {2,3,4};
int digitPins2[numOfDigits2] = {5,6};

void setup() {
  disp1.setDigitPins(numOfDigits1,digitPins1);
  disp1.setDigitDelay(2000);
  disp1.setTimer(1);
  disp1.startTimer();
 
  disp2.setDigitPins(numOfDigits2,digitPins2);
  disp2.setDigitDelay(2000);
  disp2.setTimer(2);
  disp2.startTimer();
 
  disp1.setCommonCathode();
  disp2.setCommonCathode();
 

}

void loop() {
  disp2.write(23);
  TimerFreeTone(13, 500, 500);
 for (int i = 180; i >= 0; i--){
 
            disp1.writeClock(i);
            delay(100);
 }
 
}

ISR(TIMER1_COMPA_vect){
  disp1.interruptAction();
}

ISR(TIMER2_COMPA_vect){
  disp2.interruptAction();
}

So, you're saying if you comment out *JUST* this line: TimerFreeTone(13, 500, 500); it hangs?  Could it be that it's always hanging but you don't realize it?  In other words, the audio indicator is telling you it's hanging, but it's also hanging without TimerFreeTone you just can't tell because there's no audio indication?

TimerFreeTone doesn't use timer interrupts.  But, it does use delayMicroseconds.  If SevenSeg.h messes with the Arduino timer in such a way that it messes up delayMicroseconds, it could cause a problem.

Have you tried contacting the SevenSeg.h authors?  TimerFreeTone is very simple, the entire code is here to review:

https://bitbucket.org/teckel12/arduino-timer-free-tone/src/f56f6255785f167adf28193ac78c1f62f2aa8405/TimerFreeTone.cpp?at=master

It does nothing fancy, sets the pin to output mode, calculates the square wave duration (frequency) and note duration (loops) and runs a "for" statement for however many loops are required to plan the tone for the specified length of time.

My guess is that SevenSeg is messing with the delayMicroseconds command and that's causing the problem.

Tim
My platforms Arduino, Teensy 3.2, Arduino Pro Mini, ATmega328
My libraries: NewPing, LCDBitmap, toneAC, toneAC2, NewTone, TimerFreeTone
My projects: https://dogblocker.com & https://baconorbeer.com
My beer: Great Lakes Brewing Co. Lake Erie Monster

Grumpy_Mike

I am trying to use TimerFreeTone, and while it works it seems to choke on some duration values. I was using a pot to increase the duration and found that certain values produce a much shorter tone than that given in the duration parameter.
I striped down the code to just a fixed values, it is here:-
Code: [Select]
#include <TimerFreeTone.h>
const byte tonePin = 3;
void setup() {
pinMode(tonePin,OUTPUT);
}

void loop() {
  makeStep();
  delay(20);
}

void makeStep(){
  TimerFreeTone(tonePin,440, 400);
}

What happens is that it just outputs two cycles of the tone and then stops short. So the duration of the tone is 3.5mS not the 400 given in the duration parameter.
I have tried IDE 1.6.5 and IDE 1.6.7 with the same results. I used a Uno as well as an ATtiny85 and I still get this choking. Other values work as expected, but some result in this short tone.
Any ideas?

teckel

I am trying to use TimerFreeTone, and while it works it seems to choke on some duration values. I was using a pot to increase the duration and found that certain values produce a much shorter tone than that given in the duration parameter.
I striped down the code to just a fixed values, it is here:-
Code: [Select]
#include <TimerFreeTone.h>
const byte tonePin = 3;
void setup() {
pinMode(tonePin,OUTPUT);
}

void loop() {
  makeStep();
  delay(20);
}

void makeStep(){
  TimerFreeTone(tonePin,440, 400);
}

What happens is that it just outputs two cycles of the tone and then stops short. So the duration of the tone is 3.5mS not the 400 given in the duration parameter.
I have tried IDE 1.6.5 and IDE 1.6.7 with the same results. I used a Uno as well as an ATtiny85 and I still get this choking. Other values work as expected, but some result in this short tone.
Any ideas?
I've got a guess.  Try the following beta and let me know if that works:

http://www.leethost.com/link_pics/TimerFreeTone_v1.3BETA.zip

Tim
My platforms Arduino, Teensy 3.2, Arduino Pro Mini, ATmega328
My libraries: NewPing, LCDBitmap, toneAC, toneAC2, NewTone, TimerFreeTone
My projects: https://dogblocker.com & https://baconorbeer.com
My beer: Great Lakes Brewing Co. Lake Erie Monster

pjrc

Tim, will this create inaccurate (lower) frequencies if a significant number of interrupts occur during the delayMicroseconds() calls?

For example, someone could use Serial.print() right before calling TimerFreeTone().  Or they could have another library like Servo or FreqMeasure running, which is constantly servicing interrupts from the timers.  In fact, if they need this timer-free code, it's a safe bet they probably have a project already doing something useful with all the timers, which often involves interrupts.

Wouldn't it be better to read micro() at the beginning, and then in busy loops for the delays, so you can automatically adapt to interrupts consuming CPU time?

teckel

Tim, will this create inaccurate (lower) frequencies if a significant number of interrupts occur during the delayMicroseconds() calls?

For example, someone could use Serial.print() right before calling TimerFreeTone().  Or they could have another library like Servo or FreqMeasure running, which is constantly servicing interrupts from the timers.  In fact, if they need this timer-free code, it's a safe bet they probably have a project already doing something useful with all the timers, which often involves interrupts.

Wouldn't it be better to read micro() at the beginning, and then in busy loops for the delays, so you can automatically adapt to interrupts consuming CPU time?
That may work better in certain conditions for sure, but with additional overhead.  This library is by no means accurate in frequency nor duration if there's a lot of other interrupts happening.  I guess it's designed to simply "work" but with the understanding that accuracy is out.

I would use this library for simple "beep" indications when all timers are used.  Not really good no matter how the duration is calculated if the user is trying to play a melody while other interrupts are triggering.  The length may be a bit more accurate with your suggestion, but the frequency will still be off.

Tim
My platforms Arduino, Teensy 3.2, Arduino Pro Mini, ATmega328
My libraries: NewPing, LCDBitmap, toneAC, toneAC2, NewTone, TimerFreeTone
My projects: https://dogblocker.com & https://baconorbeer.com
My beer: Great Lakes Brewing Co. Lake Erie Monster

teckel

Tim, will this create inaccurate (lower) frequencies if a significant number of interrupts occur during the delayMicroseconds() calls?

For example, someone could use Serial.print() right before calling TimerFreeTone().  Or they could have another library like Servo or FreqMeasure running, which is constantly servicing interrupts from the timers.  In fact, if they need this timer-free code, it's a safe bet they probably have a project already doing something useful with all the timers, which often involves interrupts.

Wouldn't it be better to read micro() at the beginning, and then in busy loops for the delays, so you can automatically adapt to interrupts consuming CPU time?
Thanks for the suggestion.  I did some testing and the overhead is lower in a "while" loop checking millis() than a "for" loop.  I figured it would be the other way around.  Anyway, I'll probably take your advice and change the way the note duration is calculated and looped.

Tim
My platforms Arduino, Teensy 3.2, Arduino Pro Mini, ATmega328
My libraries: NewPing, LCDBitmap, toneAC, toneAC2, NewTone, TimerFreeTone
My projects: https://dogblocker.com & https://baconorbeer.com
My beer: Great Lakes Brewing Co. Lake Erie Monster

GCMan

Hi Tim,

I have this error while compiling for ATtiny13A in Arduino 1.6.9:


error: 'portModeRegister' was not declared in this scope

   uint8_t *portMode = (uint8_t *) portModeRegister(digitalPinToPort(pin));             // Get the port mode register for the pin.

exit status 1
Error compiling for board Attiny 13A standalone 9.6Mhz.

No issue when I compile for Arduino Uno.

Can this library run on Attiny13A?

Thanks.

GC

teckel

Can this library run on Attiny13A?
Sounds like whatever core you're using to compile for the ATtiny13A doesn't support port register calls.  My library will work on the ATtiny13A if the core includes port register compatibility (which it appears to not).

I tried to duplicate it, but I can't find in the Arduino IDE board manager where you load the ATtiny13A core.  Probably because it's not officially supported by Arduino.  So, what core are you using for the ATtiny13A?

Tim
My platforms Arduino, Teensy 3.2, Arduino Pro Mini, ATmega328
My libraries: NewPing, LCDBitmap, toneAC, toneAC2, NewTone, TimerFreeTone
My projects: https://dogblocker.com & https://baconorbeer.com
My beer: Great Lakes Brewing Co. Lake Erie Monster

spicajames

I've been using this lib and it's great, I'm using it together with a servo and rgb leds.

I'm wondering if we could control volume by software?

teckel

I'm wondering if we could control volume by software?
A single-channel digital potentiometer would work.  Or, you could try this beta release of TimerFreeTone

TimerFreeTone v1.4 - Added optional volume parameter

It's untested, but my guess is that it will work.  The syntax for setting the volume is simple, just add another parameter at the end of TimerFreeTone with a volume range of 0 to 10 (0=off, 10=full volume).  Below are the details:

SYNTAX:
  TimerFreeTone( pin, frequency, duration [, volume ] ) - Play a note on pin at frequency in Hz for duration in milliseconds.
    Parameters:
      * pin        - Pin speaker is wired to (other wire to ground, be sure to add an inline 100 ohm resistor).
      * frequency  - Play the specified frequency (should work fairly well in the 100 to 15000 Hz range).
      * duration   - Set the duration to play in milliseconds. Range: 0 to 65535 (65.5 seconds).
      * volume     - Optionally set the tone volume level (from 0 to 10), defaults to full volume (10).


Let me know if and how it works!


Tim
My platforms Arduino, Teensy 3.2, Arduino Pro Mini, ATmega328
My libraries: NewPing, LCDBitmap, toneAC, toneAC2, NewTone, TimerFreeTone
My projects: https://dogblocker.com & https://baconorbeer.com
My beer: Great Lakes Brewing Co. Lake Erie Monster


Go Up