C - Hertz to Seconds and how to get the proper time for a delay?

3.5k views Asked by At

I am playing around with a PIC 24 and currently I have a bit of a problem with converting hertz to seconds and then using it as a delay for a signal send to a piezo piece(buzzer, zoomer, speaker, etc) and I would like to make it play certain notes.

I want to know if I am doing my conversion from hertz to seconds(mseconds in the code provided) and if I am doing my signal processing properly.

Here is the code where I am troubled with:

My hertz to int conversion:

int16 note(int i)
{
   float time = (1.0/i);

   int fin = ((time/2) * 1000);

   return fin;
}

and here is how I send the signal to the pic24 I am using:

void main()
{
InitMCU();

 output_high(PIN_D1);
 delay_ms(note(E));
 output_low(PIN_D1);
 delay_ms(200); 
 output_high(PIN_D1);
 delay_ms(note(E));
 output_low(PIN_D1);
 delay_ms(200);
}

Here is how I have defined the notes:

#define C 255 //do
#define D 227 //re
#define E 204 //mi
#define F 191
#define G 170
#define A 153
#define B 136
#define C2 127
3

There are 3 answers

0
old_timer On BEST ANSWER

first off you are trying to use square waves to make sine waves. So it is always going to sound a bit wrong if you aim specifically for the target frequency with the timer. make a 440Hz square wave it doesnt "sound" like a 440Hz sine wave. Maybe the physics will round it off but, I am betting not as much as you want.

You can if you have the speed. Do the one bit DAC thing from the 90s or whenever that was. If you can make your square waves go faster than the speaker can physically move you can say sprinkle more ones than zeros for a while say pushing the speaker out a bit at a controlled rate then more zeros than ones for a while teasing the speaker in. The speaker becomes a physical low pass filter. You can probably use the PWM in the microcontroller to help with this. But you need to dynamically change it and this is PIC so you are likely to run out of resources before you can code up a lot of tables for clean sound.

To do the square wave thing, you need to change the output pin at half the frequency. Do not do the calculations at runtime, do then on your calculator or let the toolchain do it. Say you are running your processor/timer at 1Mhz, and you want 440Hz on a pin. You need the period to be 1/440. hz is cycles per second so inverting that makes it seconds per cycle. 0.00227272 (repeating) seconds per cycle or period, so you need it high for half and low for half (or vice versa, doesnt matter) so that means 0.00113636... between output state changes. If your timer was 1Mhz that is 1/1Million seconds per cycle. Or one microsecond. how many microseconds in 0.00113636... 1136. So every 1136 timer ticks you change state, you read the docs on the timer and have it count down or up or whatever 1136 counts (usually these are zero based numbers so 1135 and then count the zero and then the interrupt or status flag or whatever). you can also probably poll a counter that counts to/from all ones to/from all zeros and rolls over, subtract then from now and mask it with the number of bits that are counting and the difference is the time. with a 16 bit counter (start-now)&0xFFFF is the difference so long as your desired time is enough less than 0xFFFF. 1136 would certainly be. wait until the start minus now (if it is a down counter or now mins start if up counter).

Pre-compute your count times for a half cycle of each of these frequencies you want. when you make a tone you have to in some way loop every cycle on for the half period, off for half period, on for half period off for half period.

Depending on the speaker and your frequency it should sorta work. If you want to try the one bit thing

https://en.wikipedia.org/wiki/1-bit_DAC

you could start with a triangle wave do a say 66% duty cycle for 1/4th of the 1136 microseconds, a 33% duty cycle for 1/2 of the 1136 microseconds then 66% for the final 1/4th. Or one time do a 1/4th then do 1/2 at one duty cycle then 1/2 at another. You should be able to find or write a low pass filter, not that you would know what the speaker properties are but you can get a feel for how to generate the slower wave within the higher. After a triangle you could try a trapezoid. ramp up at some rate, do a 50% for a bit, then ramp down, repeat for the other half of the period.

For experimental purposes, find or pre-compute a sequence covering the whole period, you could have code that generates some few hundred or thousand lines of code, with a pic you can be fairly if not exactly deterministic X number of microseconds can be achived by executing exactly Y number of instructions or the right mix of Y instructions like

on
on
off
on
off
on
on
off

then the last instruction jumps to the top, again experimental, but you may find if you do it right you get a cleaner sound, perhaps much cleaner than a square wave.

you can add a simple R/C filter, literally two components, to convert your bit stream into an analog waveform that you then feed the speaker or amplify then feed the speaker, the feature here is you can look at it on a scope.

The other path you can take on the cheap and easy is a resistor ladder, instead of one bit you output basically the analog value for that place in the period and use a resistor ladder to turn that into an analog signal.

Or just use a real dac. based on how fast you can feed the dac pre-compute the number of values you need to send for one period, and make a table and just send them, in a loop until you are done with that frequency.

so with all that back to your code. you are trying to use some I guess millisecond library function? so lets say that is what you want to do.

440Hz as we saw is 0.00113636 seconds for a half period, so that would be 1millisecond on then one millisecond off, your code should do this

for(i=0;i<nperiods;i++)
{
on
delay_ms(1)
off
delay_ms(1)
}

any other delays in there just make it wrong...for a square wave. for others then I doubt you will have hardcoded delays.

So there are a number of problems with the above, first off a millisecond delay is way to slow for what you are trying you need a microsecond delay and you need to understand how long it takes for the overhead in the loop, our math showed 1136 microseconds for 440hz, with some truncating of accuracy. but the code that does the delay, esp on a slow mcu like this takes many clock cycles, if this is C code and not asm then many many more, plus the code to turn the gpio pin on and off, you have to subtract/tune those out. A scope will help, if you start off with 1136 and the scope is showing a period of 3000us instead of 2272.7 then you need to subtract that out and try again. so a delay of 772 instead of 1136. that kind of thing.

Middle C is 261.6Hz according to google, right or wrong lets run with it. that is 3823 microseconds. feeding your note function 255, which I assume is what the defines are for gives 1.9 I assume milliseconds. which is right as far as milliseconds goes. which gets truncated to 1 millisecond or 2000 microseconds which is 500hz which is of course way off. that define should have been 261 or 262 not 255 anyway right?

hmmm so you are trying to make a high pulse then a fixed length low pulse of 200ms? so if you fed it note E and assumed that the code took no time to run. it would be high for 2 ms then low for 200, and assuming you repeated that that is a 1% duty cycle at the frequency of 1/201ms or 4.97...Hz, the piano frequencies

https://en.wikipedia.org/wiki/Piano_key_frequencies

dont show a note at 5hz. I am sure it is close to some harmonic down there, but pretty low.

A would be 3ms high 200 low or a frequency of 1/203ms or 4.9hz

Other than the math, doing runtime floating point on a pic (or anywhere that doesnt have an fpu) is extremely costly, the math alone probably takes longer than your entire cycle. absolutely no reason to compute that runtime. you could have easily made the defines with the math or used your calculator and made the defines with hardcoded hand computed numbers. It still wouldnt have worked with a fixed low period esp one that is so significantly large compared to the numbers you need. with millisecond delay assuming the code ran instantly. on delay 1ms off delay 1ms is 500hz. on, delay 2, off delay 2, 250hz, delay 3 is 166, delay 4 is 125 and so on. you are not going to hit many real notes assuming the code ran instantly which it doesnt and using a millisecond delay.

to make the pressure wave you want to basically have the speaker pushed out from its resting state for half the cycle and sucked back in from its resting state for half the cycle, ideally in a sine fashion so that it slowly goes out and comes back then pulls in and goes out. going from reseting state to out only will work sure, but you still need to have the duty cycle right to get close with a square wave. so understand the timers you have, with a pic you can easily hand code some loops that burn clock cycles as they are deterministic from what I remember. start with the frequency of your processor, what is it? how many processor cycles for one period of your note? same as the 440hz to 1mhz math above. 0.0011363636 times 1million (seconds per half period times ticks per second the seconds cancel out and you get ticks per half period math works on units just like numbers) if your mcu is running at 2million clock ticks per second then it is 2million times 0.001136363636...

THEN figure out how to turn it on wait that number of cpu clocks then off and wait that number of cpu clocks. feed that into your piezo or other and see how it sounds.

If you had 16 bit registers which I bet you dont but assuming a 1mhz clock you would

load reg with some number
top
subtract reg,1
compare with zero
branch to top

in assembly of course. assuming one clock cycle for the subtract and compare each then two for the branch lets say that is four per loop, so 1136/4 = 284. load the register with the pre-computed value 284.

hand code some assembly

top:
gpio on
load reg,284
one:
sub reg,1
cmp reg,0
bne one
gpio off
load reg,284
two:
sub reg,1
cmp reg,0
bne two
jmp top

crude but it would get you started down the path.

if you dont have 16 bit registers but 8 bit instead, 1mhz 1136/0x100 = 4 remainder 112 that would come out quite nice if this processor takes 4 clocks per loop as I have fantasized above. each delay would be

mov reg,0xFF
A:
sub reg,1
cmp reg,0
bne A
mov reg,28
B:
sub reg,1
cmp reg,0
bne B

there are no doubt countless resources here and other places describing delay loops for the PIC family members.

You could just wing it and see the sounds change

#define DELX 300
volatile unsigned int x;
while(1)
{
    output_high(PIN_D1);
    for(x=0;x<DELX;x++) continue; 
    output_low(PIN_D1);
    for(x=0;x<DELX;x++) continue; 
}

and play with different numbers for the define. the tone should change, the quality may not be that great or maybe much better than what you have now, but it should change if audible at all. there are likely cliffs that you will hit I assume it is an 8 bit processor so counting to 200 is WAYY different then counting to 300, it is not going to be a frequency that is one and a half times lower. possible it wont be linear, might be depends on the compiler, but it might be linear segments with kinks here and there. 100 to 200 might be linear and 300 to 400 but 200 to 300 might not be.

This is probably linear though

volatile unsigned int x;
unsigned int y,z;
for(z=0;z<1000;z++)
for(y=0;y<100;y++)
{
    output_high(PIN_D1);
    for(x=0;x<z;x++) continue; 
    output_low(PIN_D1);
    for(x=0;x<z;x++) continue; 
}
2
Bill Birch On

You need a loop!

repeat for note length:
   on
   delay
   off
   delay

Also consider calculating the required period for each note off-line and enter it as an integer instead of in Hz - the CPU wont be wasting time in floating point calculations

0
n. m. could be an AI On

The middle A note is normally a 440 Hz tone. If you toggle your piezo speaker on and off 440 times per second, the delay between successive toggles is 1/880 of a second, which is 1.13636363636 ms. This shows two things:

  • your calculations are way off; and
  • timer resolution of one millisecond is way too coarse for this application.

Of course you need a loop to play a tone with some duration.