TimerFreeTone Library v1.5: Play tones without timers and therefore no conflicts

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

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

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

GCMan:
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

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?

spicajames:
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

Yes, it works great!

Thanks.

spicajames:
Yes, it works great!

Thanks.

I did some testing last night and changed the volume array in TimerFreeTone.cpp so it works a bit better. The line you should change in the beta is this:

uint8_t _tft_volume[] = { 255, 200, 150, 125, 100, 87, 50, 33, 22, 2 }; // Duty for linear volume control.

I'll be releasing v1.4 today so you can also download it which will include this modification.

Tim

TimerFreeTone v1.4 was released adding an optional volume parameter. Get it here:

TimerFreeTone v1.4

Tim

TimerFreeTone v1.4 and the example doesn't work for me. No compile errors.
No sound at all. Using one of your other libs or standart tone works.
I need no timer sound, or using timer0 (pins 5 or 6) without breaking time functions.
I need pins 9,10 and 3,11 for high frequency PWM

mexus:
TimerFreeTone v1.4 and the example doesn't work for me. No compile errors.
No sound at all. Using one of your other libs or standart tone works.
I need no timer sound, or using timer0 (pins 5 or 6) without breaking time functions.
I need pins 9,10 and 3,11 for high frequency PWM

You can't use timer 0 without breaking time functions, so your only option is using TimerFreeTone. Are you using the example sketch and not getting sound output? Have you tried using a different pin (A0 is a good one to try). The example is using pin 10, which you should avoid as you're doing something with the PWM.

Anyway, my guess would be a timer conflict, and simply using a different pin would work. But, I'd need to see your sketch as well as a description of how you have things connected. I'll verify everything works later today, but I believe the example sketch works without a problem.

Tim

mexus:
TimerFreeTone v1.4 and the example doesn't work for me. No compile errors.
No sound at all. Using one of your other libs or standart tone works.
I need no timer sound, or using timer0 (pins 5 or 6) without breaking time functions.
I need pins 9,10 and 3,11 for high frequency PWM

Seems that something changed in the latest release of the Arduino IDE that caused TimerFreeTone to totally fail. I've updated it to version 1.5 which corrects the problem.

Let me know if there's any issues.

Tim

Thank you, Tim!
Now it works. It doesn't break my timers and PWM outputs :).
Great, just what I've needed for my project.

Shouldn't unsigned int duration be unsinged long duration?
I replaced it in your lib. So that it can work even TCCR0B = TCCR0B & B11111000 | B00000001.
Setting clock 0 to this makes one second last 64000 instead of 1000 millis. This causes your function to overflow. If you are working with millis() than you should use unsigned long not unsigned int.

mexus:
Shouldn't unsigned int duration be unsinged long duration?
I replaced it in your lib. So that it can work even TCCR0B = TCCR0B & B11111000 | B00000001.
Setting clock 0 to this makes one second last 64000 instead of 1000 millis. This causes your function to overflow. If you are working with millis() than you should use unsigned long not unsigned int.

Duration is set to unsigned int because a note duration of up to 65.5 seconds seemed acceptable, reduces the while loop execution time, and it saves a few bytes of programming space. It's only looking at duration for the comparison of millis()-startTime, so it should never roll over in real-world situations.

If you're messing with clock 0, all kinds of wacky stuff could happen. unsigned int is plenty long (65.5 seconds). If you're changing how long a second is, you should probably not use any libraries as all will assume certain standards exist (like a second is 1000 millis).

I see no reason to change it to unsigned long as it would slow down the while loop. Also, no one should ever be messing with timer 0 anyway and 65.5 seconds is plenty long for a note to play.

Tim

Hi, this is good stuff. I have been using on an ESP8266 and I thought you may appreciate my minor additions for a new version if of any use.

As I understand it current recommendations state portions of code that may last longer than 20 millis require a yield() or delay(0) within, to allow the ESP8266 to perform housekeeping tasks for Wifi safely.

With the above in mind I offer you a potential solution below. This does affect tonal quality but makes the library safer to use on this platform.

 while(millis() - startTime < duration) { // Loop for the duration.
 
 #ifdef __AVR__
 *pinOutput |= pinBit;    // Set pin high.
 delayMicroseconds(duty); // Square wave duration (how long to leave pin high).
 *pinOutput &= ~pinBit;   // Set pin low.
 #elif defined(ESP8266)
 GPOS = (1 << pin); // Set pin high.
 delayMicroseconds(duty); // Square wave duration (how long to leave pin high).
 GPOC = (1 << pin); // Set pin low
 yield();
 #else
 digitalWrite(pin,HIGH);  // Set pin high.
 delayMicroseconds(duty); // Square wave duration (how long to leave pin high).
 digitalWrite(pin,LOW);   // Set pin low.
 #endif
 delayMicroseconds(frequency - duty); // Square wave duration (how long to leave pin low).
 
 }

Cheers

neutralvibes:
Hi, this is good stuff. I have been using on an ESP8266 and I thought you may appreciate my minor additions for a new version if of any use.

As I understand it current recommendations state portions of code that may last longer than 20 millis require a yield() or delay(0) within, to allow the ESP8266 to perform housekeeping tasks for Wifi safely.

With the above in mind I offer you a potential solution below. This does affect tonal quality but makes the library safer to use on this platform.

 while(millis() - startTime < duration) { // Loop for the duration.

#ifdef AVR
*pinOutput |= pinBit;    // Set pin high.
delayMicroseconds(duty); // Square wave duration (how long to leave pin high).
*pinOutput &= ~pinBit;  // Set pin low.
#elif defined(ESP8266)
GPOS = (1 << pin); // Set pin high.
delayMicroseconds(duty); // Square wave duration (how long to leave pin high).
GPOC = (1 << pin); // Set pin low
yield();
#else
digitalWrite(pin,HIGH);  // Set pin high.
delayMicroseconds(duty); // Square wave duration (how long to leave pin high).
digitalWrite(pin,LOW);  // Set pin low.
#endif
delayMicroseconds(frequency - duty); // Square wave duration (how long to leave pin low).

}




Cheers

I would't think that would be required in this case as it's not doing anything that would prevent the normal WiFi interrupts in the middle of a tone being generated. I understand when that could be useful, but I don't think it's useful in this context. Does communications still work even when a note is playing?

Tim

Hello, my first post here....

Thanks for your work on this libary

I have a Project on an Feather M0 BLE board and have change the 1.5 Libary: The Piezo is connectet to two pins and the Status for high / low is rversed on the pins to get a louder level. Thie first impression was fine. I removed also unused code. What do you think about that?

A Question: Does this libary play the sound in background? I mean, does the code works on while a Sound is playing?

Thanks, Peter

// ---------------------------------------------------------------------------
// Created by Tim Eckel - teckel@leethost.com
// Copyright 2016 License: GNU GPL v3 http://www.gnu.org/licenses/gpl-3.0.html
//
// See "TimerFreeTone.h" for purpose, syntax, version history, links, and more.
// ---------------------------------------------------------------------------

#include "TimerFreeTone.h"

uint8_t _tft_volume[] = { 255, 200, 150, 125, 100, 87, 50, 33, 22, 2 }; // Duty for linear volume control.

void TimerFreeTone(uint8_t pin1, uint8_t pin2, unsigned long frequency, unsigned int duration, uint8_t volume) {
	if (frequency == 0 || volume == 0) { // If frequency or volume are zero, just wait duration and exit.
		delay(duration);
		return;
	} 
	frequency = 1000000 / frequency;                              // Calculate the square wave length (in microseconds).
	uint32_t duty = frequency / _tft_volume[min(volume, 10) - 1]; // Calculate the duty cycle (volume).
	pinMode(pin1, OUTPUT);                                                       // Set pin to output mode.
	pinMode(pin2, OUTPUT);

	uint32_t startTime = millis();           // Starting time of note.
	while(millis() - startTime < duration) { // Loop for the duration.
		digitalWrite(pin1,HIGH);  // Set pin high.
		digitalWrite(pin2, LOW);		
		delayMicroseconds(duty); // Square wave duration (how long to leave pin high).
		digitalWrite(pin1,LOW);   // Set pin low.
		digitalWrite(pin2, HIGH);   // Set pin low.
		delayMicroseconds(frequency - duty); // Square wave duration (how long to leave pin low).
	}
}

You kind of combined TimerFreeTone with my toneAC library. The purpose of TimerFreeTone is to not use timers, so no, it doesn't play in the background. To do that, you must use timers.

I'm guessing you found my TimerFreeTone library because you can't use typical libraries that use timers with the ARM processor on the Feather M0 board. This is one of the reasons I don't purchase these off boards. Sure, they can do simple things, but you really can't use most Arduino libraries as they haven't made them very compatible. They support the basics, but nothing else.

This is why I suggest to only purchase Arduino AVR boards or if you want more speed/features to get a Teensy 3.x board. Not only is the Teensy 3.x platform very fast, very inexpensive, and capable of doing all sorts of things, Paul Stoffregen spends the time to make them VERY compatible with most Arduino libraries or works with the library authors to make them compatible.

The other problem using ARM-based platforms is that the voltage is 3.3v instead of 5. While this is the direction of modern devices, there's lots of legacy stuff that uses 5v. Also, in the case of a speaker, 3.3v isn't much. It's probably best to build an amp for the speaker instead of what you're doing. Then, look into the timers of your chip to see if there's a library you can use to interface with it to create sound. Seriously, it would probably be easier to do Bluetooth and sound playing in the background easier with an Arduino Uno that with the Feather M0 board.

Also, I wouldn't suggest anyone using an Arduino AVR to use your code, it's too slow so you'll have a long time with both pins high going to the piezo. My library uses shift registers which are MUCH faster to almost totally avoid this problem.

I'll release a TimerFreeToneAC which will do what you want but also be safer for Arduino AVR boards.

Tim