I need help converting sensor readings into a tone. For example, say I take the y-axis reading off two accelerometers and want to use the one reading (y1) to control pitch and the other (y2) to control volume. I wanted to do this using PWM. I'm using a SAMD51 on a SparkFun Thing Plus (sorry, I know, not an "arduino", I hope that's okay on this forum).
What I've been trying to code myself is to use one timer/counter (TCC1) to control an ultrasonic frequency pulse with variable duty cycle, and a second timer/counter (TC2) to periodically raise and lower that duty cycle (of TCC1) at hearable frequencies. In effect, I'm trying to use two timers to produce a sawtooth sound wave. (Edit: It doesn't have to be TCC1 or TC2; any numbered TC/C is fine.)
My thought was that I'd use the pitch control reading (y1) to control the second counter (TC2) and the volume control reading (y2) to control some scale factor on TCC1's duty cycle.
Is this a bad way to do what I'm trying to do? Is there something simpler?
I've really struggled to find good examples of using the TCC on the SAMD51 to actually modulate pulse width. Does someone know of, or could write for me, a sample sketch that does this? I've seen MartinL's sketch in this thread: Metro M4 Express ATSAMD51 PWM Frequency and Resolution - #2 by MartinL But it doesn't include the interrupts needed to flip the value of a pin using digitalWrite.
Hi Idahowalker, thanks for the reply. Unless I'm missing something, I don't think the questions about scale are relevant to my problem. If you can get one scale (mapping from sensor outputs to pitch and intensity), you can get a wide range of scales with some simple math (like rescaling constants).
The Tone library isn't helpful, as tone uses a TC, not a TCC, and on the SAMD51 you can't use a TC to actually vary pulse widths (it's a fixed 50% duty cycle). (Edit: The tone function as written in that library has no volume control, and can't, because it uses a TC, not a TCC; all it does is produce a fixed 50/50 HIGH/LOW pulse at hearable frequencies. It doesn't actually do PWM.)
My question really boils down to: (a) What are the interrupts and other things I need to add to MartinL's code in order to flip HIGH/LOW on a digitalWrite pin, or (b) is there some easier way to directly control wave shape (height, frequency, and shape) besides using a TCC counter to do PWM and a second counter to actually control the hearable oscillation in pulse width?
I think you think that by using the PWM you will get a signal that changes in volume, you will not. All that happens is that close to the very narrowest pulses the sound timber changes a bit, that is all.
Changing the amplitude requires you to multiply a signal by a fraction between 0 and 1.0, the signal can be derived by using a look up table and the change in frequency by scanning that look up table with a fractional integer, incremental again by a fraction.
The final signal is applied to the PWM using an ultrasonic frequency.
Hi Grumpy_Mike, sorry for being unclear (I was trying to keep my post brief). I know that changing the duty cycle on a square wave with fixed (hearable) frequency will not change the volume (intensity) of the sound. That's not my idea.
I think what you say fits with what I said, although I see how I might not have been clear (or maybe I'm really confused). You said: "Changing the amplitude requires you to multiply a signal by a fraction between 0 and 1.0"; I said: "... My thought was that I'd use the pitch control reading (y1) to control the second counter (TC2) and the volume control reading (y2) to control some scale factor on TCC1's duty cycle."; by using a "scale factor", I meant "multiply ... by a fraction", i.e. if y2 has a max value of Y, use the fraction y2/Y.
I get that, in principle, I only need one timer to generate a hearable signal. That's what the tone function does, it generates a square wave at a frequency in the audible range. I want to use two timers for two reasons.
A square wave off a digital pin can't be multiplied by a scale factor. It's just a HIGH/LOW signal with two possible values. So, no volume control is possible.
I'm hoping to get something besides a square wave (which sounds bad), like a sawtooth or something that approximates a sine wave.
So, my plan is to use two timers: A first time to generate an ultrasonic square wave with variable duty cycle (e.g., at 240kHz), and then a second timer to vary that duty cycle in an oscillation with hearable frequency. For example, tie the duty cycle the ratio of the timer count (COUNT) to time top value (TOP), COUNT/TOP. So, as the timer goes up, the duty cycle increases, until the timer resets, at which point the duty cycle falls to zero. Over very small periods of time, the speaker "sees" the average voltage, which is HIGH*DUTYCYCLE, so the speaker will "see" a voltage oscillating at some hearable frequency (and thus produce a sound).
I don't follow this part: "the signal can be derived by using a look up table and the change in frequency by scanning that look up table with a fractional integer, incremental again by a fraction. ... The final signal is applied to the PWM using an ultrasonic frequency." I'm not sure if we're talking past each other, or if you're offering me a much better way to achieve what I want (and I just don't follow).
Why not post the code you've written? Then you can get some concrete feedback. Some design notes would help, too, like the timer frequencies and resolutions, expectations of sound quality, etc...
In terms of concrete coding, I'm just trying to get the hang of using a TCC to do PWM, i.e. to actually be able to vary the duty cycle of a square wave. Going off that post from MartinL linked above, I have:
//Aim: Set-up digital pin 11 to output 400Hz, square wave with a 50% duty cycle, so that the duty cycle can be adjusted
//Step 1: Setup a generic clock and connect it to a TCC
//Set up the generic clock (GCLK7) to clock timer TCC0
GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) | // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK7
GCLK_GENCTRL_SRC_DFLL; // Select 48MHz DFLL clock source
while (GCLK->SYNCBUSY.bit.GENCTRL7); // Wait for synchronization
GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN | // Enable the TCC0 peripheral channel (p. 169)
GCLK_PCHCTRL_GEN_GCLK7; // Connect generic clock 7 to TCC0
//Step 2: Something with the ports and multiplexer needs to be configured?? I don't fully understand why MartinL has these two lines, or what pin I should be putting here. The pin I want to output my square wave on?
// Enable the peripheral multiplexer on PORT??
PORT->Group[g_APinDescription[11].ulPort].PINCFG[g_APinDescription[11].ulPin].bit.PMUXEN = 1;
// Set the PORT?? peripheral multiplexer to peripheral (even port number) E(6): TCC0, Channel 0
PORT->Group[g_APinDescription[11].ulPort].PMUX[g_APinDescription[11].ulPin >> 1].reg |= PORT_PMUX_PMUXE(6);
//Step 3: Setup the TCC itself and its behavior (how it counts, its top value, and its CC value.
TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV2 | // Set prescaler to 2, 48MHz/2 = 24MHz (see p. 1,613)
TC_CTRLA_PRESCSYNC_PRESC; // Set the reset/reload to trigger on prescaler clock
TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM; // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)
while (TCC0->SYNCBUSY.bit.WAVE) // Wait for synchronization
TCC0->PER.reg = 59999; // Set-up the PER (period) register 50Hz PWM
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
TCC0->CC[0].reg = 29999; // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
while (TCC0->SYNCBUSY.bit.CC0); // Wait for synchronization
TCC0->CTRLA.bit.ENABLE = 1; // Enable timer TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
Step 4, what I don't know how to do, is make this TCC actually do stuff, like toggle my digital 11 pin between HIGH/LOW. What I want is for pin 11 to be HIGH while the TCC is counting up to its CC value, and LOW between the CC and the reset at the top. I want to do it through interrupts, so its not chewing up a bunch of processor time.
If someone can help me setup this last part on this simple first step, I can probably figure out how to set up a second timer (a TC) to update the CC value (the duty cycle) of this TCC. (Or, perhaps I'm deeply confused and this won't work/is just gibberish.) Or, maybe there's an easier way to oscillate the CC value of this TCC at hearable frequencies, without a second timer.
In any case, I'd be thrilled just to get the above code to output a 400Hz square wave!
Thanks for everyone's patience. I am, as you can see, still learning!
Correct me if I'm wrong, but in the end you'd have to rely on the reactance of the output circuit (e.g. the inductance of the speaker coil combined with some resistance and perhaps some capacitance somewhere) to get a non-digital output, so something that will output levels between full on and full off. I'm not sure if the concept you're discussing in its current form will make much sense if you don't also include the hardware design of the output into the equation.
For what it's worth, wouldn't it be just a whole lot easier to simply use a DAC for this?
Hi Koraks, Thanks, you're right. I confused myself. The solution I had/have in mind requires some hardware to smooth the output. I knew I was relying on hardware limitations, although I wasn't completely clear on that point in my own head, and I'm sure you're right that I also need resistors and capacitors in addition to the speaker itself.
Since I posted this original question I have started to think about how to do this with the DAC on the SAMD51; maybe that is the better solution.
Thank you all for your help! This has helped me think through the issues a lot.
If anyone has additional thoughts, I should clarify an important design constraint. As I said, I want to use sensor data to modulate a sound wave (mostly pitch and volume). The key constraint isn't "sound quality", but reaction time and temporal resolution. I want to be able to sample every 1ms, maybe every 10ms at most, and update the sound wave, with minimal latency between sensor reading and sound wave update. (I do realize that, at this time scale, changes in pitch will only be meaningful if the main frequency of the soundwave is fairly high, so I'm largely working with tones in the 2-4kHz range, although I don't think changes in volume at this time scale are limited to a certain pitch range.)
You should use a timer to generate a sample which involves getting the right number from the lookup table, and then advancing the lookup pointer by a specific amount. Wrapping round the pointer when it overflows the lookup table. Your data each time you get it will simply change the lookup table pointer increment.
If you see my book http://www.apress.com/9781484217207
Chapter 12 is all about generating waveforms.
You can download the book's software from this link. Listing 12-1 generates a saw tooth wave, using the PWM timer for the A/D, and Listing 12-3 generates a funky output which is one cycle of sin followed by 2 cycles of saw tooth, using a MCP4921 D/A on the SI bus. This is better to look at on a scope than to listen to but it illustrates the point. Those two examples are for an Arduino Uno, with other processors the timers are likely to be a bit different.
Sorry I can't post the pictures due to copyright issues.