tone(), noTone()

enif
Fri Aug 14, 2015 1:26 pm
When trying to port my little “car blink sound booster” program to the Maple Mini, I realized that there are no tone() and noTone() functions available yet in the STM32-Arduino library – or at least, I could not find them…

Thus, here is my first attempt to implement Arduino compatible versions of the tone() and noTone() functions. Be warned that this is my first attempt dealing with the STM32 timers, so the code might still be far from optimal. However, it seems to work in my program, and it also tested well with the Arduino toneMelody.ino example:

///////////////////////////////////////////////////////////////////////
//
// tone(pin,frequency[,duration]) generate a tone on a given pin
//
// noTone(pin) switch of the tone on the pin
//
///////////////////////////////////////////////////////////////////////

#include "Arduino.h"
#include <HardwareTimer.h>

#ifndef TONE_TIMER
#define TONE_TIMER 2
#endif

HardwareTimer tone_timer(TONE_TIMER);

bool tone_state = true; // last pin state for toggling
short tone_pin = -1; // pin for outputting sound
short tone_freq = 444; // tone frequency (0=pause)
unsigned tone_micros = 500000/444; // tone have wave time in usec
int tone_counts = 0; // tone duration in units of half waves

// timer hander for tone with no duration specified,
// will keep going until noTone() is called
void tone_handler_1(void) {
tone_state = !tone_state;
digitalWrite(tone_pin,tone_state);
}

// timer hander for tone with a specified duration,
// will stop automatically when duration time is up.
void tone_handler_2(void) { // check duration
if(tone_freq>0){
tone_state = !tone_state;
digitalWrite(tone_pin,tone_state);
}
if(!--tone_counts){
tone_timer.pause();
pinMode(tone_pin, INPUT);
}
}

// play a tone on given pin with given frequency and optional duration in msec
void tone(uint8_t pin, unsigned short freq, unsigned duration = 0) {
tone_pin = pin;
tone_freq = freq;
tone_micros = 500000/(freq>0?freq:1000);
tone_counts = 0;

tone_timer.pause();

if(freq >= 0){
if(duration > 0)tone_counts = ((long)duration)*1000/tone_micros;
pinMode(tone_pin, OUTPUT);

// set timer to half period in microseconds
tone_timer.setPeriod(tone_micros);

// Set up an interrupt on channel 1
tone_timer.setChannel1Mode(TIMER_OUTPUT_COMPARE);
tone_timer.setCompare(TIMER_CH1, 1); // Interrupt 1 count after each update
tone_timer.attachCompare1Interrupt(tone_counts?tone_handler_2:tone_handler_1);

// Refresh the tone timer
tone_timer.refresh();

// Start the timer counting
tone_timer.resume();
} else
pinMode(tone_pin, INPUT);
}

// disable tone on specified pin, if any
void noTone(uint8_t pin){
tone(pin,-1);
}


RogerClark
Fri Aug 14, 2015 8:57 pm
@enif

Thanks

After we know its stable, I will integrate it into the core.


victor_pv
Sat Aug 15, 2015 2:10 am
Enif, half the pins in the maple mini can be set as PWM output to one of the timers. A few can not, though, and would rather need a software implementation to get a tone on them. But for the ones that can be set as output of a timer channel, it could be easier to get them to generate the frequency directly that way.

Look at the leaflabs hardware timer documentation for that.

If you want to find which Timer device and timer channel corresponds to a certain pin, you can use pin_map, such as:

For the timer device:
timer_dev * mytimer = PIN_MAP[Pin].timer_device;

And for the channel number:
uint8 mychannel = PIN_MAP[Pin].timer_channel;

You can then manipulate the registers directly, or you can create a HardwareTimer object corresponding to that timer_device, with something like:
uint8 timer_num = (mytimer – TIMER1) + 1;
HardwareTimer My_timer_object(timer_num);

So you set the pin as PWM output, and then manipulate the timer and timer_channel settings (or the hardware timer object) corresponding to that timer to adjust the frequency you want.
I believe the leaflabs documentation may have some example of generating a certain frequency on a pin.

For any ping for which PIN_MAP returns NULL for the timer_device or timer_channel, means that there is no hardware timer associated with it.
You can rely on the use to check that, or perhaps check for it in your code.


enif
Sat Aug 15, 2015 6:42 am
Thanks a lot, Victor. I was vaguely aware of the relation between the timers and the PWM pins, but it helps to have the actual coding how to access the corresponding timers and channels from the program.

My implementation is indeed just a quick+dirty approach to at least get my Nano sketch working on the Maple Mini. But I thought that it might still be useful to post it under “code snipplets”, as it might also help others until an official version of tone()/noTone() will become available…

I am sure that generating the tone directly with the timer/channel that is associated with the given pin would be more efficient. However, I am afraid that the problem would remain, that even so, using tone() would still disturb the PWM output on the other 3 pins sharing the same timer. So my worry is less getting the most efficient code, but rather being able to minimize the interference it causes with other uses of the timers. So my idea was at least to have some possibility use an alternate timer (with #define TONE_TIMER), in case the default timer is disturbing PWM pins needed by the program.

How would one implement tone() on a given timer/channel without compromising the PWM on the other pins associated with that timer?


victor_pv
Sat Aug 15, 2015 1:01 pm
enif wrote:Thanks a lot, Victor. I was vaguely aware of the relation between the timers and the PWM pins, but it helps to have the actual coding how to access the corresponding timers and channels from the program.

My implementation is indeed just a quick+dirty approach to at least get my Nano sketch working on the Maple Mini. But I thought that it might still be useful to post it under “code snipplets”, as it might also help others until an official version of tone()/noTone() will become available…

I am sure that generating the tone directly with the timer/channel that is associated with the given pin would be more efficient. However, I am afraid that the problem would remain, that even so, using tone() would still disturb the PWM output on the other 3 pins sharing the same timer. So my worry is less getting the most efficient code, but rather being able to minimize the interference it causes with other uses of the timers. So my idea was at least to have some possibility use an alternate timer (with #define TONE_TIMER), in case the default timer is disturbing PWM pins needed by the program.

How would one implement tone() on a given timer/channel without compromising the PWM on the other pins associated with that timer?


RogerClark
Sat Aug 15, 2015 9:10 pm
AFIK

Ports have 4 registers

The control reg, to set the pin mode
The ODR, Output data register (lower 16 bits only)
The IDR, Input data register ( again only the lower 16 bits)
The BSSR, which can set and reset individual bits ( its split into the upper and lower 16 bits. I cant remember off the top of my head, which 16 bits is the Set pattern, and which 16 bits is the reset (low) pattern. But using the BSSR is the fastest way to set or reset individual bits, as it doesnt require the code to readback the existing bit pattern before you OR in ( or AND in) a new pattern. Hence its about 3 times faster.
So I’d recommend if you are doing an ISR based tone generator, just have one variable with the BSSR pattern to set the bit, and another var which stores the reset pattern, and set those up when the tone is started, and use them in your ISR to set or reset the bit in question.


victor_pv
Sun Aug 16, 2015 2:16 am
enif wrote:… So my worry is less getting the most efficient code, but rather being able to minimize the interference it causes with other uses of the timers. So my idea was at least to have some possibility use an alternate timer (with #define TONE_TIMER), in case the default timer is disturbing PWM pins needed by the program.

How would one implement tone() on a given timer/channel without compromising the PWM on the other pins associated with that timer?


enif
Sun Aug 16, 2015 7:15 pm
Thanks for your feedback and input, Victor and Roger!

I have done some more thinking about this, and ended up with a different implementation which only uses one channel of one timer and does not affect the PWM operation of the pins sharing the same timer. The idea is to keep the timer itself unchanged, i.e. not changing its period, but leaving it free running on the 16 bit count register (which is what PWM uses). The frequency is generated by modifying the channel’s compare register on the fly inside the interrupt handler, kind of “hopping” from half wave to halve wave. It becomes a bit more complicated when the frequency is less than 550Hz (=36000000/65536), since then more than one interrupt must pass before the output is toggled, so the period of one half wave is divided in junks of less than 65536/36000000 sec.

Using the info given by Victor, the current code will use the corresponding PWM timer/channel for PWM pins, and a default timer/channel for the other pins. This default is now set to Timer4/Channel4, since that one is not used by any PWM pin. However,
I don’t know at all, whether that channel is already used for something else (Do you know??). If it isn’t, we could just as well use Timer4/Channel4 for all the pins… In any case, I have also added a function setToneTimerChannel(timer,channel) to optionally force using a specific timer/channel combination.

I have also implemented the BSRR register write, as suggested by Roger. If macro USE_BSRR is defined, BSRR is used, otherwise uses digitalWrite() – which might be useful for testing and benchmarking.

///////////////////////////////////////////////////////////////////////
//
// tone(pin,frequency[,duration]) generate a tone on a given pin
//
// noTone(pin) switch off the tone on the pin
//
// setToneTimerChannel(timer,channel) force use of given timer/channel
//
///////////////////////////////////////////////////////////////////////

#include "Arduino.h"
#include <HardwareTimer.h>

// define default timer and channel
#ifndef TONE_TIMER
#define TONE_TIMER 4
#endif
#ifndef TONE_CHANNEL
#define TONE_CHANNEL 4
#endif

#define PinTimer(pin) (PIN_MAP[pin].timer_device->clk_id-RCC_TIMER1+1)
#define PinChannel(pin) (PIN_MAP[pin].timer_channel)

// if USE_PIN_TIMER is set, the PWM timer/channel is used for PWM pins
#define USE_PIN_TIMER

// if USE_BSRR is set the tone pin will be written via the fast BSRR register
// instead of using the slow digitalWrite() function in the interrupt handler
#define USE_BSRR

// construct static timer array (
HardwareTimer TTimer1(1), TTimer2(2), TTimer3(3), TTimer4(4);
#ifdef STM32_HIGH_DENSITY
HardwareTimer TTimer5(5), TTimer6(6), TTimer7(7), TTimer8(8);
#endif
HardwareTimer *TTimer[4] {
&TTimer1,&TTimer2,&TTimer3,&TTimer4
#ifdef STM32_HIGH_DENSITY
,&TTimer5,&TTimer6,&TTimer7,&TTimer8
#endif
};

uint8_t tone_force_channel = 0; // forced timer channel
uint8_t tone_force_ntimer = 0; // forced timer

HardwareTimer *tone_timer = TTimer[TONE_TIMER]; // timer used to generate frequency
uint8_t tone_channel = TONE_CHANNEL; // timer channel used to generate frequency
uint8_t tone_ntimer = TONE_TIMER; // timer used to generate frequency

bool tone_state = true; // last pin state for toggling
short tone_pin = -1; // pin for outputting sound
short tone_freq = 444; // tone frequency (0=pause)
uint32_t tone_nhw = 0; // tone duration in number of half waves
uint16_t tone_tcount = 0; // time between handler calls in 1/36 usec
uint16_t tone_ncount = 0; // handler call between toggling
uint16_t tone_n = 0; // remaining handler calls before toggling
uint32_t tone_next = 0; // counter value of next interrupt

#ifdef USE_BSRR
volatile uint32_t *tone_bsrr; // BSRR set register (lower 16 bits)
uint32_t tone_smask=0; // BSRR set bitmask
uint32_t tone_rmask=0; // BSRR reset bitmask
#endif

////////////////////////////////////////////////////////////////////////////////
// timer hander for tone with no duration specified,
// will keep going until noTone() is called
void tone_handler_1(void) {
tone_next += tone_tcount; // comparator value for next interrupt
tone_timer->setCompare(tone_channel, tone_next); // and install it
if(--tone_n == 0){
tone_state = !tone_state; // toggle tone output

#ifdef USE_BSRR
if(tone_state)
*tone_bsrr = tone_smask;
else
*tone_bsrr = tone_rmask;
#else
digitalWrite(tone_pin,tone_state);// and output it
#endif

tone_n = tone_ncount; // reset interrupt counter
}
}

////////////////////////////////////////////////////////////////////////////////
// timer hander for tone with a specified duration,
// will stop automatically when duration time is up.
void tone_handler_2(void) {
tone_next += tone_tcount;
tone_timer->setCompare(tone_channel, tone_next);
if(--tone_n == 0){
if(tone_freq>0){ // toggle pin
tone_state = !tone_state;
#ifdef USE_BSRR
if(tone_state)
*tone_bsrr = tone_smask;
else
*tone_bsrr = tone_rmask;
#else
digitalWrite(tone_pin,tone_state);// and output it
#endif
}
tone_n = tone_ncount;
if(!--tone_nhw){ // check if tone duration has finished
tone_timer->pause(); // disable timer
pinMode(tone_pin, INPUT); // disable tone pin
}
}
}

////////////////////////////////////////////////////////////////////////////////
// play a tone on given pin with given frequency and optional duration in msec
void tone(uint8_t pin, short freq, unsigned duration = 0) {
tone_pin = pin;

#ifdef USE_PIN_TIMER
// if the pin has a PWM timer/channel, use it (unless the timer/channel are forced)
if(PinChannel(tone_pin) && !tone_force_channel){
tone_channel = PinChannel(tone_pin);
tone_ntimer = PinTimer(tone_pin);
} else
#endif
{
// set timer and channel to default resp values forced with setToneTimerChannel
tone_ntimer = tone_force_channel?tone_force_ntimer:TONE_TIMER;
tone_channel = tone_force_channel?tone_force_channel:TONE_CHANNEL;
}

tone_timer = TTimer[tone_ntimer-1];
tone_freq = freq;
tone_nhw = 0;
tone_next = 0;

tone_timer->pause();

if(freq > 0 || duration >0 ){
uint32_t count = 18000000/freq; // timer counts per half wave
tone_ncount = tone_n = (count>>16)+1; // number of 16-bit count chunk
tone_tcount = count/tone_ncount; // size of count chunk
if(duration > 0) // number of half waves to be generated
tone_nhw = ((duration*(freq>0?freq:100))/1000)<<1;
else // no duration specified, continuous sound until noTone() called
tone_nhw = 0;

pinMode(tone_pin, PWM); // configure output pin
pinMode(tone_pin, OUTPUT); // configure output pin

#ifdef USE_BSRR
// Set up BSRR register values for fast ISR
tone_bsrr = &((PIN_MAP[tone_pin].gpio_device)->regs->BSRR);
tone_smask = (BIT(PIN_MAP[tone_pin].gpio_bit));
tone_rmask = tone_smask<<16;
#endif

// Set up an interrupt on given timer and channel
tone_next = tone_tcount; // prepare channel compare register
tone_timer->setMode(tone_channel,TIMER_OUTPUT_COMPARE);
tone_timer->setCompare(tone_channel,tone_next);
// attach corresponding handler routine
tone_timer->attachInterrupt(tone_channel,tone_nhw?tone_handler_2:tone_handler_1);

// Refresh the tone timer
tone_timer->refresh();

// Start the timer counting
tone_timer->resume();

} else {

// detach handler routine
tone_timer->detachInterrupt(tone_channel);
// disactive pin by configuring it as input
pinMode(tone_pin, INPUT);

}
}

////////////////////////////////////////////////////////////////////////////////
// disable tone on specified pin, if any
void noTone(uint8_t pin){
tone(pin,-1); // it's all handled in tone()
}

////////////////////////////////////////////////////////////////////////////////
// set timer and channel to some different value
// must be called before calling tone() or after noTone() was called
void setToneTimerChannel(uint8_t ntimer, uint8_t channel){
tone_force_ntimer = ntimer;
tone_force_channel = channel;
}


victor_pv
Sun Aug 16, 2015 8:25 pm
That channel goes to pb9, which is used for discovery in the maple mini. So that is safe enough in the mini, as that pin should never be used for PWM in that board, but on other boards it may be used.
You may want to put a warning comment or something, for people using a different maple, or one of the many other stm32 boards available, but is nice that you already found a way to affect only 1 timer channel :)

ekawahyu
Mon Jul 11, 2016 4:00 pm
@enif: What is the status of this work? Is it available somewhere on github and stable?

enif
Sun Jul 17, 2016 3:00 pm
@ekawahyu: No, it’s still just that “code snipplet” you see in my message above. I use it in some of my programs and have not run into any problems – so far…

ahull
Sat Nov 19, 2016 10:39 pm
I added a couple of working examples – see this thread -> http://www.stm32duino.com/viewtopic.php … 202#p20202

@Roger, not sure how easy it would be to add tone() noTone() to the arduino core based on enif’s code, but I think it would be a useful addition.


RogerClark
Sat Nov 19, 2016 11:26 pm
Thanks andy

I agree that tone() and notone() should be added.

I took a quick look at that implementation and I think it seemed OK.

And as its a new file its not going to break anything as long as it compiles, so I may as well add it ;-)


RogerClark
Sun Nov 20, 2016 7:25 am
ekawahyu wrote:@enif: What is the status of this work? Is it available somewhere on github and stable?

Nutsy
Wed Dec 07, 2016 12:13 am
hias, So ive been playing with Tone a little, i have a piezo on my project now. And i wanted a nice little beep sound for startup and menu button presses… But! I cant get any decent sounds from my piezo using tone. I did a little code to scale through the notes defined, and while there is a slight harmonic tone going up the scale its mostly distorted loud beeps.

ill try and record something to show at some point.

Im powering the piezo at 12v and triggered from the maple mini via a standard 2n4001 transistor.

Going by the specs 12V should be fine. I think it said 3-20v or something like that.

At this stage i guess im not really asking for help. More just saying my experience so far. But if you have any ideas on how to improve it let me know.


RogerClark
Wed Dec 07, 2016 12:23 am
I’m not sure you are ever going to get a nice sound from a piezo. I know some of them are harmonically tuned to work best at one specific frequency (or harmonics of that frequency), so they sometimes have 2 peaks in their audio spectrum.

If you are driving via a transistor you may want to look at an alternative device which is more like a normal speaker. There are some small beepers which are not piezo which you could use, (but they often take a lot of current – e.g. 50 to 100mA and need a driver transistor)

Even then, if you feed with a square wave, you are going to get a specific type of sound, but you could try adding an RC network between the STM32 and the transistor to form a low pass filter e.g. set to 10kHz or perhaps 5kHz, as that would give things a more pleasing sound.


ahull
Wed Dec 07, 2016 11:03 am
If you only want a specific frequency of “beep”, you could convert the square wave to a sine wave with passives.

http://www.learningaboutelectronics.com … ircuit.php

You may also find that PWM of the tone gives better results, (attenuation control to remove might lessen the worst of the distortion) but that would involve more work on the tone() functions, or writing your own.

You could of course just use a 12V beeper designed to produce a single tone. Let us know how you get on.


Nutsy
Wed Dec 07, 2016 12:51 pm
Thanks for the advice guys, just to throw a spanner into the works *cries* Ive reread the listing. Since I ordered the buzzers the listings been updated 3-5v… So the voltage was listed wrong before. And im supplying 12v to a 5v buzzer. no wonder it sounds distorted.

Oh well I can live with this for now. Something to change on the final design.


ahull
Wed Dec 07, 2016 3:06 pm
Nutsy wrote:Thanks for the advice guys, just to throw a spanner into the works *cries* Ive reread the listing. Since I ordered the buzzers the listings been updated 3-5v… So the voltage was listed wrong before. And im supplying 12v to a 5v buzzer. no wonder it sounds distorted.

Oh well I can live with this for now. Something to change on the final design.


Nutsy
Wed Dec 07, 2016 4:02 pm
i guess piezos are pretty robust… I mean hit one they can generate a thousand volts… So i guess it wont smoke it… but just distorts :p

I dont think theres any active circuitry in the thing. Just wired into a peizo… i might be able to hack the piezo and wire it to the 5v rail… rather than the 12v


Nutsy
Wed Dec 07, 2016 4:24 pm
Heh hacked the 5v line onto it… Much better :D Still not perfect but better. I can only guess then that 12v was just over loading the gain signal :p

aramperez
Tue Aug 08, 2017 2:30 am
I’m using PA0 to connect to a piezo buzzer and PA1 as a PWM signal to control a motor (via an L9110S chip). Whenever I use tone(PA0, xxx);, it messes up the PWM on PA1. Anyone know why this is the case?

Thanks,
Aram


victor_pv
Tue Aug 08, 2017 4:01 am
[aramperez – Tue Aug 08, 2017 2:30 am] –
I’m using PA0 to connect to a piezo buzzer and PA1 as a PWM signal to control a motor (via an L9110S chip). Whenever I use tone(PA0, xxx);, it messes up the PWM on PA1. Anyone know why this is the case?

Thanks,
Aram

Please check the datasheet, and the libmaple documentation on PWM for details, but basically is due to both pins having channels from the same Timer device.


RogerClark
Tue Aug 08, 2017 4:51 am
There are only 4 timers in the F103C, these are shared across groups of pins.

If you look in the Maple mini documentation you can see which timers are shared across which pins

http://docs.leaflabs.com/static.leaflab … -mini.html

(I’m sure the same information is in the reference manual for the STM32F103 (all 1000+ pages of it), if you want to read definitive reference


aramperez
Tue Aug 08, 2017 6:04 pm
Thanks Victor and Roger, I’ll look at the documentation and move one of the signals.

/Aram


Leave a Reply

Your email address will not be published. Required fields are marked *