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);
}
Thanks
After we know its stable, I will integrate it into the core.
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.
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?
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?
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.
How would one implement tone() on a given timer/channel without compromising the PWM on the other pins associated with that timer?
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;
}
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
@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.
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 ![]()
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.
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.
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.
Oh well I can live with this for now. Something to change on the final design.
Oh well I can live with this for now. Something to change on the final design.
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
Thanks,
Aram
[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.
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
/Aram


